Skip to content

Commit abdb361

Browse files
committed
Initial commit
0 parents  commit abdb361

File tree

26 files changed

+1616
-0
lines changed

26 files changed

+1616
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.{kt,kts,gradle}]
10+
indent_style = space
11+
indent_size = 4
12+
continuation_indent_size = 4

.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Hidden files
2+
.*
3+
4+
# Temporary files
5+
*~
6+
7+
# Git
8+
!.git*
9+
10+
# EditorConfig
11+
!.editorconfig
12+
13+
# IntelliJ Idea
14+
out/
15+
*.iml
16+
*.ipr
17+
*.iws
18+
19+
# Travis CI
20+
!.travis.yml
21+
22+
# Gradle
23+
build/
24+
25+
# JVM error logs
26+
hs_err_pid*.log
27+
replay_pid*.log

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
language: java
2+
jdk:
3+
- openjdk8
4+
5+
notifications:
6+
email: false

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2019 Michael Bull (https://www.michael-bull.com)
2+
3+
Permission to use, copy, modify, and/or distribute this software for any
4+
purpose with or without fee is hereby granted, provided that the above
5+
copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# kotlin-retry
2+
3+
[![Release](https://api.bintray.com/packages/michaelbull/maven/kotlin-retry/images/download.svg)](https://bintray.com/michaelbull/maven/kotlin-retry/_latestVersion) [![Build Status](https://travis-ci.org/michaelbull/kotlin-retry.svg?branch=master)](https://travis-ci.org/michaelbull/kotlin-retry) [![License](https://img.shields.io/github/license/michaelbull/kotlin-retry.svg)](https://github.com/michaelbull/kotlin-retry/blob/master/LICENSE)
4+
5+
[`retry`][retry] is a higher-order function for retrying operations that may fail.
6+
7+
```kotlin
8+
retry(limitAttempts(10) + constantDelay(delayMillis = 50L)) {
9+
/* your code */
10+
}
11+
```
12+
13+
## Installation
14+
15+
```groovy
16+
repositories {
17+
maven { url = 'https://dl.bintray.com/michaelbull/maven' }
18+
}
19+
20+
dependencies {
21+
compile 'com.michael-bull.kotlin-retry:kotlin-retry:1.0.0'
22+
}
23+
```
24+
25+
## Introduction
26+
27+
IO operations often experience temporary failures that warant re-execution,
28+
e.g. a database transaction that may fail due to a deadlock.<sup>[[1]][innodb-deadlocks][[2]][postgres-deadlocks]</sup>
29+
30+
> _“even if your application logic is correct, you must still handle the case
31+
> where a transaction must be retried”_
32+
>
33+
> _[Deadlocks in InnoDB][innodb-deadlocks]_
34+
35+
The [`retry`][retry] function simplifies this process by wrapping the
36+
application logic and applying a specified [`RetryPolicy`][retry-policy].
37+
38+
In the example below, either of the calls to `customers.nameFromId` may fail,
39+
abandoning the remaining logic within the `printExchangeBetween` function. As
40+
such, we may want to retry this operation until 5 attempts in total have been
41+
executed:
42+
43+
```kotlin
44+
suspend fun printExchangeBetween(a: Long, b: Long) {
45+
val customer1 = customers.nameFromId(a)
46+
val customer2 = customers.nameFromId(b)
47+
println("$customer1 exchanged with $customer2")
48+
}
49+
50+
fun main() = runBlocking {
51+
retry(limitAttempts(5)) {
52+
printExchangeBetween(1L, 2L)
53+
}
54+
}
55+
```
56+
57+
We can also provide a [`RetryPolicy`][retry-policy] that only retries failures
58+
of a specific type. The example below will retry the operation only if the
59+
reason for failure was a `SQLDataException`, pausing for 20 milliseconds before
60+
retrying and stopping after 5 total attempts.
61+
62+
```kotlin
63+
val retryTimeouts: RetryPolicy<Throwable> = {
64+
if (reason is SQLDataException) RetryImmediately else StopRetrying
65+
}
66+
67+
suspend fun printExchangeBetween(a: Long, b: Long) {
68+
val customer1 = customers.nameFromId(a)
69+
val customer2 = customers.nameFromId(b)
70+
println("$customer1 exchanged with $customer2")
71+
}
72+
73+
fun main() = runBlocking {
74+
retry(retryTimeouts + limitAttempts(5) + constantDelay(20)) {
75+
printExchangeBetween(1L, 2L)
76+
}
77+
}
78+
```
79+
80+
## Backoff
81+
82+
The examples above retry executions immediately after they fail, however we may
83+
wish to spread out retries out with an ever-increasing delay. This is known as
84+
a "backoff" and comes in many forms. This library includes all the forms of
85+
backoff strategy detailed the article by Marc Brooker on the AWS Architecture
86+
Blog entitled ["Exponential Backoff And Jitter"][aws-backoff].
87+
88+
#### Binary Exponential
89+
90+
> _“exponential backoff means that clients multiply their backoff by a constant
91+
> after each attempt, up to some maximum value”_
92+
>
93+
> ```
94+
> sleep = min(cap, base * 2 ** attempt)
95+
> ```
96+
97+
```kotlin
98+
retry(limitAttempts(5) + binaryExponentialBackoff(base = 10L, max = 5000L)) {
99+
/* code */
100+
}
101+
```
102+
103+
#### Full Jitter
104+
105+
> _“trying to improve the performance of a system by adding randomness ... we
106+
> want to spread out the spikes to an approximately constant rate”_
107+
>
108+
> ```
109+
> sleep = random_between(0, min(cap, base * 2 ** attempt))
110+
> ```
111+
112+
```kotlin
113+
retry(limitAttempts(5) + fullJitterBackoff(base = 10L, max = 5000L)) {
114+
/* code */
115+
}
116+
```
117+
118+
#### Equal Jitter
119+
120+
> _“Equal Jitter, where we always keep some of the backoff and jitter by a
121+
> smaller amount”_
122+
>
123+
> ```
124+
> temp = min(cap, base * 2 ** attempt)
125+
> sleep = temp / 2 + random_between(0, temp / 2)
126+
> ```
127+
128+
```kotlin
129+
retry(limitAttempts(5) + equalJitterBackoff(base = 10L, max = 5000L)) {
130+
/* code */
131+
}
132+
```
133+
134+
#### Decorrelated Jitter
135+
136+
> _“Decorrelated Jitter, which is similar to “Full Jitter”, but we also
137+
> increase the maximum jitter based on the last random value”_
138+
>
139+
> ```
140+
> sleep = min(cap, random_between(base, sleep * 3))
141+
> ```
142+
143+
```kotlin
144+
retry(limitAttempts(5) + decorrelatedJitterBackoff(base = 10L, max = 5000L)) {
145+
/* code */
146+
}
147+
```
148+
149+
## Inspiration
150+
151+
- [Control.Retry](http://hackage.haskell.org/package/retry-0.8.0.1/docs/Control-Retry.html)
152+
- [tokio_retry](https://docs.rs/tokio-retry/0.2.0/tokio_retry/)
153+
154+
## Contributing
155+
156+
Bug reports and pull requests are welcome on [GitHub][github].
157+
158+
## License
159+
160+
This project is available under the terms of the ISC license. See the
161+
[`LICENSE`](LICENSE) file for the copyright information and licensing terms.
162+
163+
[github]: https://github.com/michaelbull/kotlin-retry
164+
[retry]: https://github.com/michaelbull/kotlin-retry/blob/master/src/main/kotlin/com/github/michaelbull/retry/Retry.kt
165+
[innodb-deadlocks]: https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
166+
[postgres-deadlocks]: https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS
167+
[retry-policy]: https://github.com/michaelbull/kotlin-retry/blob/master/src/main/kotlin/com/github/michaelbull/retry/policy/RetryPolicy.kt
168+
[aws-backoff]: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
169+
[haskell-retry]: http://hackage.haskell.org/package/retry-0.8.0.1/docs/Control-Retry.html

build.gradle.kts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
2+
import com.jfrog.bintray.gradle.BintrayExtension
3+
import com.jfrog.bintray.gradle.tasks.BintrayUploadTask
4+
import org.jetbrains.dokka.gradle.DokkaTask
5+
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
6+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
7+
8+
val SourceSet.kotlin: SourceDirectorySet
9+
get() = withConvention(KotlinSourceSet::class) { kotlin }
10+
11+
fun BintrayExtension.pkg(configure: BintrayExtension.PackageConfig.() -> Unit) {
12+
pkg(delegateClosureOf(configure))
13+
}
14+
15+
plugins {
16+
`maven-publish`
17+
kotlin("jvm") version ("1.3.50")
18+
id("com.github.ben-manes.versions") version ("0.22.0")
19+
id("com.jfrog.bintray") version ("1.8.4")
20+
id("org.jetbrains.dokka") version ("0.9.18")
21+
id("net.researchgate.release") version ("2.8.1")
22+
}
23+
24+
repositories {
25+
mavenCentral()
26+
jcenter()
27+
maven(url = "https://dl.bintray.com/michaelbull/maven")
28+
}
29+
30+
dependencies {
31+
implementation(kotlin("stdlib"))
32+
implementation("com.michael-bull.kotlin-result:kotlin-result:1.1.3")
33+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0")
34+
testImplementation(enforcedPlatform("org.junit:junit-bom:5.5.1"))
35+
testImplementation("org.junit.jupiter:junit-jupiter")
36+
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0")
37+
testImplementation("io.mockk:mockk:1.9.3")
38+
}
39+
40+
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
41+
resolutionStrategy {
42+
componentSelection {
43+
all {
44+
val rejected = listOf("alpha", "beta", "rc", "cr", "m", "eap").any {
45+
candidate.version.contains(it, ignoreCase = true)
46+
}
47+
48+
if (rejected) {
49+
reject("Release candidate")
50+
}
51+
}
52+
}
53+
}
54+
}
55+
56+
tasks.withType<KotlinCompile> {
57+
kotlinOptions {
58+
jvmTarget = "1.8"
59+
freeCompilerArgs = listOf("-Xuse-experimental=kotlin.contracts.ExperimentalContracts")
60+
}
61+
}
62+
63+
tasks.withType<Test> {
64+
failFast = true
65+
useJUnitPlatform()
66+
}
67+
68+
val sourcesJar by tasks.registering(Jar::class) {
69+
group = LifecycleBasePlugin.BUILD_GROUP
70+
description = "Assembles a jar archive containing the main classes with sources."
71+
archiveClassifier.set("sources")
72+
from(project.the<SourceSetContainer>().getByName("main").allSource)
73+
}
74+
75+
val dokka by tasks.existing(DokkaTask::class) {
76+
sourceDirs = project.the<SourceSetContainer>().getByName("main").kotlin.srcDirs
77+
outputFormat = "javadoc"
78+
outputDirectory = "$buildDir/docs/javadoc"
79+
kotlinTasks(::defaultKotlinTasks)
80+
}
81+
82+
val javadocJar by tasks.registering(Jar::class) {
83+
group = LifecycleBasePlugin.BUILD_GROUP
84+
description = "Assembles a jar archive containing the Javadoc API documentation."
85+
archiveClassifier.set("javadoc")
86+
dependsOn(dokka)
87+
from(dokka.get().outputDirectory)
88+
}
89+
90+
publishing {
91+
publications {
92+
register("mavenJava", MavenPublication::class) {
93+
from(components["java"])
94+
artifact(javadocJar.get())
95+
artifact(sourcesJar.get())
96+
}
97+
}
98+
}
99+
100+
val bintrayUser: String? by project
101+
val bintrayKey: String? by project
102+
103+
bintray {
104+
user = bintrayUser
105+
key = bintrayKey
106+
setPublications("mavenJava")
107+
108+
pkg {
109+
repo = "maven"
110+
name = project.name
111+
desc = project.description
112+
websiteUrl = "https://github.com/michaelbull/kotlin-retry"
113+
issueTrackerUrl = "https://github.com/michaelbull/kotlin-retry/issues"
114+
vcsUrl = "[email protected]:michaelbull/kotlin-retry.git"
115+
githubRepo = "michaelbull/kotlin-retry"
116+
setLicenses("ISC")
117+
}
118+
}
119+
120+
val bintrayUpload by tasks.existing(BintrayUploadTask::class) {
121+
dependsOn("build")
122+
dependsOn("generatePomFileForMavenJavaPublication")
123+
dependsOn(sourcesJar)
124+
dependsOn(javadocJar)
125+
}
126+
127+
tasks.named("afterReleaseBuild") {
128+
dependsOn(bintrayUpload)
129+
}

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
group=com.michael-bull.kotlin-retry
2+
version=1.0.0-SNAPSHOT

gradle/wrapper/gradle-wrapper.jar

54.3 KB
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)