Skip to content

Commit c846f9e

Browse files
bradnewmanRobWin
authored andcommitted
Add resilience4j-kotlin module (ReactiveX#441)
1 parent 82a1f43 commit c846f9e

File tree

18 files changed

+1146
-3
lines changed

18 files changed

+1146
-3
lines changed

README.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ Add-on modules
5959
* resilience4j-ratpack: Ratpack Starter
6060
* resilience4j-retrofit: Retrofit adapter
6161
* resilience4j-feign: Feign adapter
62+
* resilience4j-vertx: Vertx Future decorator
6263
* resilience4j-consumer: Circular Buffer Event consumer
64+
* resilience4j-kotlin: Kotlin coroutines support
6365

6466
== Spring Boot demo
6567

libraries.gradle

100644100755
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ext {
2727
micrometerVersion = '1.1.4'
2828
hibernateValidatorVersion = '6.0.16.Final'
2929
wiremockVersion = '2.22.0'
30+
kotlinCoroutinesVersion = '1.2.0'
3031

3132
libraries = [
3233
// compile
@@ -111,7 +112,11 @@ ext {
111112
jaxws: "com.sun.xml.ws:jaxws-ri:2.3.2",
112113

113114
// Groovy
114-
groovy: "org.codehaus.groovy:groovy-all:2.5.6"
115+
groovy: "org.codehaus.groovy:groovy-all:2.5.6",
116+
117+
// Kotlin addon
118+
kotlin_stdlib: "org.jetbrains.kotlin:kotlin-stdlib-jdk8",
119+
kotlin_coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}"
115120
]
116121

117122
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
=== Kotlin
2+
3+
==== Introduction
4+
5+
Integration for https://kotlinlang.org/[Kotlin] coroutines that enables executing and decorating `suspend` functions with the various resilience aspects provided by the core modules.
6+
7+
Extension functions that accept Kotlin suspend functions are provided for rate limiter, retry, circuit breaker, time limiter, and semaphore-based bulkheads. No extensions for thread pool bulkheads or caches are currently provided.
8+
9+
==== Gradle
10+
11+
Add the Kotlin module of Resilience4j to your compile dependency, along with whichever core modules are needed:
12+
13+
[source,groovy, subs="attributes"]
14+
----
15+
repositories {
16+
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
17+
mavenCentral()
18+
}
19+
20+
21+
dependencies {
22+
compile('io.github.resilience4j:resilience4j-kotlin:{release-version}')
23+
// also have a dependency on the core module(s) needed - for example, retry:
24+
compile('io.github.resilience4j:resilience4j-retry:{release-version}')
25+
}
26+
----
27+
28+
==== Basic Usage
29+
30+
Two extension functions are declared for each of `CircuitBreaker`, `RateLimiter`, `Retry`, and `TimeLimiter`: one to execute a suspend function, and one to decorate a suspend function.
31+
32+
[source,kotlin]
33+
----
34+
val circuitBreaker = CircuitBreaker.ofDefaults()
35+
val result = circuitBreaker.executeSuspendFunction {
36+
// call suspending functions here
37+
}
38+
----
39+
40+
[source,kotlin]
41+
----
42+
val function = circuitBreaker.decorateSuspendFunction {
43+
// call suspending functions here
44+
}
45+
val result = function()
46+
----
47+
48+
The suspend functions suspend where usage of the normal methods would block. For example, calls to a `RateLimiter` which need to be delayed to fit within the rate limit suspend before the given function is executed.
49+
50+
No changes are made to the coroutine context of the given suspend functions.
51+
52+
===== Bulkhead
53+
54+
Decorating a suspend function with a `Bulkhead` does not add any additional suspension points. If `maxWaitTime` is non-zero, the call will *block* until the max wait time is reached or permission is obtained. For this reason, it is not recommended to use this extension function with Bulkheads with non-zero max wait times.
55+
56+
No extension functions for thread-pool based bulkheads are provided.
57+
58+
===== Circuit Breaker
59+
60+
Decorating a suspend function with a `CircuitBreaker` does not add any additional suspension points.
61+
62+
===== Rate Limiter
63+
64+
Suspend functions decorated with a `RateLimiter` use `delay()` in order to suspend before executing the decorated function if the rate limit has been reached.
65+
66+
===== Retry
67+
68+
Suspend functions decorated with a `Retry` use `delay()` in order to suspend between retries.
69+
70+
===== Time Limiter
71+
72+
The `TimeLimiter` extension functions simply use `withTimeout()` from `kotlinx-coroutines`, using the timeout from the receiver's configuration. Specifically, this means:
73+
74+
1. On timeout, a `TimeoutCancellationException` is raised, rather than a `TimeoutException` as with methods for non-suspending functions.
75+
1. When a timeout occurs, the coroutine is cancelled, rather than the thread being interrupted as with methods for non-suspending functions.
76+
1. After the timeout, the given block can only be stopped at a cancellable suspending function call.
77+
1. The `cancelRunningFuture` configuration setting is ignored - on timeout, the suspend function is always cancelled even if the `cancelRunningFuture` is set to `false`.

resilience4j-documentation/src/docs/asciidoc/addons_guide.adoc

100644100755
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ include::addon_guides/dropwizard.adoc[]
1212

1313
include::addon_guides/prometheus.adoc[]
1414

15-
include::addon_guides/micrometer.adoc[]
15+
include::addon_guides/micrometer.adoc[]
16+
17+
include::addon_guides/kotlin.adoc[]

resilience4j-documentation/src/docs/asciidoc/introduction.adoc

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Add-on modules
2727
* resilience4j-retrofit: Retrofit adapter
2828
* resilience4j-feign: Feign adapter
2929
* resilience4j-vertx: Vertx Future decorator
30+
* resilience4j-kotlin: Kotlin coroutines support
3031
* resilience4j-consumer: Circular Buffer Event consumer
3132
3233
To highlight a few differences to Netflix Hystrix:

resilience4j-kotlin/build.gradle

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
3+
}
4+
5+
dependencies {
6+
implementation(libraries.kotlin_stdlib)
7+
implementation(libraries.kotlin_coroutines)
8+
9+
compileOnly(project(':resilience4j-bulkhead'))
10+
compileOnly(project(':resilience4j-circuitbreaker'))
11+
compileOnly(project(':resilience4j-ratelimiter'))
12+
compileOnly(project(':resilience4j-retry'))
13+
compileOnly(project(':resilience4j-timelimiter'))
14+
15+
testImplementation(project(':resilience4j-bulkhead'))
16+
testImplementation(project(':resilience4j-circuitbreaker'))
17+
testImplementation(project(':resilience4j-ratelimiter'))
18+
testImplementation(project(':resilience4j-retry'))
19+
testImplementation(project(':resilience4j-timelimiter'))
20+
}
21+
22+
compileKotlin {
23+
kotlinOptions.jvmTarget = "1.8"
24+
}
25+
26+
compileTestKotlin {
27+
kotlinOptions.jvmTarget = "1.8"
28+
}
29+
30+
ext.moduleName='io.github.resilience4j.kotlin'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
*
3+
* Copyright 2019: Brad Newman
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.kotlin.bulkhead
20+
21+
import io.github.resilience4j.bulkhead.Bulkhead
22+
import io.github.resilience4j.bulkhead.BulkheadConfig
23+
24+
/**
25+
* Decorates and executes the given suspend function [block].
26+
*
27+
* If [BulkheadConfig.maxWaitTime] is non-zero, *blocks* until the max wait time is reached or permission is obtained.
28+
* For this reason, it is not recommended to use this extension function with Bulkheads with non-zero max wait times.
29+
*/
30+
suspend fun <T> Bulkhead.executeSuspendFunction(block: suspend () -> T): T {
31+
acquirePermission()
32+
return try {
33+
block()
34+
} finally {
35+
onComplete()
36+
}
37+
}
38+
39+
/**
40+
* Decorates the given suspend function [block] and returns it.
41+
*
42+
* If [BulkheadConfig.maxWaitTime] is non-zero, *blocks* until the max wait time is reached or permission is obtained.
43+
* For this reason, it is not recommended to use this extension function with Bulkheads with non-zero max wait times.
44+
*/
45+
fun <T> Bulkhead.decorateSuspendFunction(block: suspend () -> T): suspend () -> T = {
46+
executeSuspendFunction(block)
47+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* Copyright 2019: Guido Pio Mariotti, Brad Newman
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.kotlin.circuitbreaker
20+
21+
import io.github.resilience4j.circuitbreaker.CircuitBreaker
22+
23+
/**
24+
* Decorates and executes the given suspend function [block].
25+
*/
26+
suspend fun <T> CircuitBreaker.executeSuspendFunction(block: suspend () -> T): T {
27+
acquirePermission()
28+
val start = System.nanoTime()
29+
try {
30+
val result = block()
31+
val durationInNanos = System.nanoTime() - start
32+
onSuccess(durationInNanos)
33+
return result
34+
} catch (exception: Exception) {
35+
val durationInNanos = System.nanoTime() - start
36+
onError(durationInNanos, exception)
37+
throw exception
38+
}
39+
}
40+
41+
/**
42+
* Decorates the given *suspend* function [block] and returns it.
43+
*/
44+
fun <T> CircuitBreaker.decorateSuspendFunction(block: suspend () -> T): suspend () -> T = {
45+
executeSuspendFunction { block() }
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* Copyright 2019: Brad Newman
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.kotlin.ratelimiter
20+
21+
import io.github.resilience4j.ratelimiter.RateLimiter
22+
import io.github.resilience4j.ratelimiter.RateLimiterConfig
23+
import io.github.resilience4j.ratelimiter.RequestNotPermitted
24+
import kotlinx.coroutines.delay
25+
import java.util.concurrent.TimeUnit
26+
27+
/**
28+
* Decorates and executes the given suspend function [block].
29+
*
30+
* If [RateLimiterConfig.timeoutDuration] is non-zero, the returned function suspends until a permission is available.
31+
*/
32+
suspend fun <T> RateLimiter.executeSuspendFunction(block: suspend () -> T): T {
33+
val waitTimeNs = reservePermission()
34+
if (waitTimeNs < 0) throw RequestNotPermitted(this)
35+
delay(TimeUnit.NANOSECONDS.toMillis(waitTimeNs))
36+
return block()
37+
}
38+
39+
/**
40+
* Decorates the given suspend function [block] and returns it.
41+
*
42+
* If [RateLimiterConfig.timeoutDuration] is non-zero, the returned function suspends until a permission is available.
43+
*/
44+
fun <T> RateLimiter.decorateSuspendFunction(block: suspend () -> T): suspend () -> T = {
45+
executeSuspendFunction(block)
46+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
*
3+
* Copyright 2019: Brad Newman
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.kotlin.retry
20+
21+
import io.github.resilience4j.retry.Retry
22+
import kotlinx.coroutines.delay
23+
24+
/**
25+
* Decorates and executes the given suspend function [block].
26+
*
27+
* Between attempts, suspends based on the configured interval function.
28+
*/
29+
suspend fun <T> Retry.executeSuspendFunction(block: suspend () -> T): T {
30+
val retryContext = asyncContext<T>()
31+
while (true) {
32+
try {
33+
val result = block()
34+
val delayMs = retryContext.onResult(result)
35+
if (delayMs < 1) {
36+
retryContext.onSuccess()
37+
return result
38+
} else {
39+
delay(delayMs)
40+
}
41+
} catch (e: Exception) {
42+
val delayMs = retryContext.onError(e)
43+
if (delayMs < 1) {
44+
throw e
45+
} else {
46+
delay(delayMs)
47+
}
48+
}
49+
}
50+
}
51+
52+
/**
53+
* Decorates the given suspend function [block] and returns it.
54+
*
55+
* Between attempts, suspends based on the configured interval function.
56+
*/
57+
fun <T> Retry.decorateSuspendFunction(block: suspend () -> T): suspend () -> T = {
58+
executeSuspendFunction(block)
59+
}

0 commit comments

Comments
 (0)