Skip to content

Commit 5773669

Browse files
authored
feat: retry config overhaul and adaptive retries (#877)
1 parent ef67792 commit 5773669

File tree

39 files changed

+2108
-518
lines changed

39 files changed

+2108
-518
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "2cca656d-2f6e-41ff-87ae-233465e3fe8d",
3+
"type": "feature",
4+
"description": "Add adaptive retry mode",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#701"
7+
]
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "c50a8d50-f66f-4598-8a58-f5c3dfc8302a",
3+
"type": "feature",
4+
"description": "**Breaking**: Simplify mechanisms for setting/updating retry strategies in client config. See [this discussion post](https://github.com/awslabs/aws-sdk-kotlin/discussions/964) for more details.",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#701"
7+
]
8+
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,9 @@ object RuntimeTypes {
114114
val Outcome = symbol("Outcome")
115115
val RetryStrategy = symbol("RetryStrategy")
116116
val StandardRetryStrategy = symbol("StandardRetryStrategy")
117-
val StandardRetryStrategyOptions = symbol("StandardRetryStrategyOptions")
118117

119118
object Delay : RuntimeTypePackage(KotlinDependency.CORE, "retries.delay") {
120-
val ExponentialBackoffWithJitter = symbol("ExponentialBackoffWithJitter")
121-
val ExponentialBackoffWithJitterOptions = symbol("ExponentialBackoffWithJitterOptions")
122119
val InfiniteTokenBucket = symbol("InfiniteTokenBucket")
123-
val StandardRetryTokenBucket = symbol("StandardRetryTokenBucket")
124-
val StandardRetryTokenBucketOptions = symbol("StandardRetryTokenBucketOptions")
125120
}
126121

127122
object Policy : RuntimeTypePackage(KotlinDependency.CORE, "retries.policy") {
@@ -178,6 +173,9 @@ object RuntimeTypes {
178173
val SdkClient = symbol("SdkClient")
179174
val AbstractSdkClientBuilder = symbol("AbstractSdkClientBuilder")
180175
val LogMode = symbol("LogMode")
176+
val RetryClientConfig = symbol("RetryClientConfig")
177+
val RetryStrategyClientConfig = symbol("RetryStrategyClientConfig")
178+
val RetryStrategyClientConfigImpl = symbol("RetryStrategyClientConfigImpl")
181179
val SdkClientConfig = symbol("SdkClientConfig")
182180
val SdkClientFactory = symbol("SdkClientFactory")
183181
val SdkClientOption = symbol("SdkClientOption")

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/ConfigPropertyType.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ package software.amazon.smithy.kotlin.codegen.rendering.util
99
* Descriptor for how a configuration property is rendered when the configuration is built
1010
*/
1111
sealed class ConfigPropertyType {
12+
companion object {
13+
val DoNotRender = Custom(render = { _, _ -> }, renderBuilder = { _, _ -> })
14+
}
15+
1216
/**
1317
* A property type that uses the symbol type and builder symbol directly
1418
*/

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/RuntimeConfigProperty.kt

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package software.amazon.smithy.kotlin.codegen.rendering.util
77

88
import software.amazon.smithy.codegen.core.CodegenException
9+
import software.amazon.smithy.codegen.core.Symbol
910
import software.amazon.smithy.codegen.core.SymbolReference
1011
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
1112
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
@@ -19,10 +20,7 @@ object RuntimeConfigProperty {
1920
name = "clientName"
2021
symbol = KotlinTypes.String
2122
baseClass = RuntimeTypes.SmithyClient.SdkClientConfig
22-
builderBaseClass = buildSymbol {
23-
name = "${baseClass!!.name}.Builder<Config>"
24-
namespace = baseClass!!.namespace
25-
}
23+
builderBaseClass = RuntimeTypes.SmithyClient.SdkClientConfig.nestedGenericBuilder
2624
documentation = """
2725
A reader-friendly name for the client.
2826
""".trimIndent()
@@ -49,18 +47,13 @@ object RuntimeConfigProperty {
4947
baseClass = RuntimeTypes.HttpClient.Config.HttpEngineConfig
5048
baseClassDelegate = Delegate(null, "builder.buildHttpEngineConfig()")
5149

52-
builderBaseClass = RuntimeTypes.HttpClient.Config.HttpEngineConfig.let {
53-
buildSymbol {
54-
name = "${it.name}.Builder"
55-
namespace = it.namespace
56-
}
57-
}
50+
builderBaseClass = RuntimeTypes.HttpClient.Config.HttpEngineConfig.nestedBuilder
5851
builderBaseClassDelegate = Delegate(
5952
RuntimeTypes.HttpClientEngines.Default.HttpEngineConfigImpl,
6053
"HttpEngineConfigImpl.BuilderImpl()",
6154
)
6255

63-
propertyType = ConfigPropertyType.Custom({ _, _ -> }, { _, _ -> })
56+
propertyType = ConfigPropertyType.DoNotRender
6457
}
6558

6659
val IdempotencyTokenProvider = ConfigProperty {
@@ -88,31 +81,29 @@ object RuntimeConfigProperty {
8881
""".trimIndent()
8982

9083
propertyType = ConfigPropertyType.RequiredWithDefault("StandardRetryPolicy.Default")
91-
baseClass = RuntimeTypes.SmithyClient.SdkClientConfig
92-
builderBaseClass = buildSymbol {
93-
name = "${baseClass!!.name}.Builder<Config>"
94-
namespace = baseClass!!.namespace
95-
}
84+
baseClass = RuntimeTypes.SmithyClient.RetryClientConfig
85+
builderBaseClass = RuntimeTypes.SmithyClient.RetryClientConfig.nestedBuilder
9686

9787
additionalImports = listOf(RuntimeTypes.Core.Retries.Policy.StandardRetryPolicy)
9888
}
9989

10090
val RetryStrategy = ConfigProperty {
101-
symbol = RuntimeTypes.Core.Retries.RetryStrategy
10291
name = "retryStrategy"
92+
symbol = RuntimeTypes.Core.Retries.RetryStrategy
10393
documentation = """
104-
The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the
105-
strategy.
94+
The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the strategy.
10695
""".trimIndent()
10796

108-
propertyType = ConfigPropertyType.RequiredWithDefault("StandardRetryStrategy()")
109-
baseClass = RuntimeTypes.SmithyClient.SdkClientConfig
110-
builderBaseClass = buildSymbol {
111-
name = "${baseClass!!.name}.Builder<Config>"
112-
namespace = baseClass!!.namespace
113-
}
97+
baseClass = RuntimeTypes.SmithyClient.RetryStrategyClientConfig
98+
baseClassDelegate = Delegate(null, "builder.buildRetryStrategyClientConfig()")
11499

115-
additionalImports = listOf(RuntimeTypes.Core.Retries.StandardRetryStrategy)
100+
builderBaseClass = RuntimeTypes.SmithyClient.RetryStrategyClientConfig.nestedBuilder
101+
builderBaseClassDelegate = Delegate(
102+
RuntimeTypes.SmithyClient.RetryStrategyClientConfigImpl,
103+
"RetryStrategyClientConfigImpl.BuilderImpl()",
104+
)
105+
106+
propertyType = ConfigPropertyType.DoNotRender
116107
}
117108

118109
val LogMode = ConfigProperty {
@@ -123,10 +114,7 @@ object RuntimeConfigProperty {
123114
propertyType = ConfigPropertyType.RequiredWithDefault("LogMode.Default")
124115

125116
baseClass = RuntimeTypes.SmithyClient.SdkClientConfig
126-
builderBaseClass = buildSymbol {
127-
name = "${baseClass!!.name}.Builder<Config>"
128-
namespace = baseClass!!.namespace
129-
}
117+
builderBaseClass = RuntimeTypes.SmithyClient.SdkClientConfig.nestedGenericBuilder
130118

131119
documentation = """
132120
Configure events that will be logged. By default clients will not output
@@ -198,3 +186,15 @@ object RuntimeConfigProperty {
198186
""".trimIndent()
199187
}
200188
}
189+
190+
private val Symbol.nestedBuilder: Symbol
191+
get() = buildSymbol {
192+
name = "${this@nestedBuilder.name}.Builder"
193+
namespace = this@nestedBuilder.namespace
194+
}
195+
196+
private val Symbol.nestedGenericBuilder: Symbol
197+
get() = buildSymbol {
198+
name = "${this@nestedGenericBuilder.name}.Builder<Config>"
199+
namespace = this@nestedGenericBuilder.namespace
200+
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/WaiterGenerator.kt

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package software.amazon.smithy.kotlin.codegen.rendering.waiters
77

88
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
99
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
10-
import software.amazon.smithy.kotlin.codegen.core.addImport
1110
import software.amazon.smithy.kotlin.codegen.core.withBlock
1211
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1312
import java.text.DecimalFormat
@@ -17,70 +16,49 @@ import java.text.DecimalFormatSymbols
1716
* Renders the top-level retry strategy for a waiter.
1817
*/
1918
private fun KotlinWriter.renderRetryStrategy(wi: WaiterInfo, asValName: String) {
20-
addImport(
21-
RuntimeTypes.Core.Retries.Delay.ExponentialBackoffWithJitterOptions,
22-
RuntimeTypes.Core.Retries.Delay.ExponentialBackoffWithJitter,
23-
RuntimeTypes.Core.Retries.StandardRetryStrategyOptions,
24-
RuntimeTypes.Core.Retries.StandardRetryStrategy,
25-
RuntimeTypes.Core.Retries.Delay.InfiniteTokenBucket,
26-
)
27-
28-
withBlock("val #L = run {", "}", asValName) {
29-
withBlock("val delayOptions = ExponentialBackoffWithJitterOptions(", ")") {
30-
write("initialDelay = #L.#T,", wi.waiter.minDelay.msFormat, KotlinTypes.Time.milliseconds)
31-
write("scaleFactor = 1.5,")
32-
write("jitter = 1.0,")
33-
write("maxBackoff = #L.#T,", wi.waiter.maxDelay.msFormat, KotlinTypes.Time.milliseconds)
19+
withBlock("val #L = #T {", "}", asValName, RuntimeTypes.Core.Retries.StandardRetryStrategy) {
20+
write("maxAttempts = 20")
21+
write("tokenBucket = #T", RuntimeTypes.Core.Retries.Delay.InfiniteTokenBucket)
22+
withBlock("delayProvider {", "}") {
23+
write("initialDelay = #L.#T", wi.waiter.minDelay.msFormat, KotlinTypes.Time.milliseconds)
24+
write("scaleFactor = 1.5")
25+
write("jitter = 1.0")
26+
write("maxBackoff = #L.#T", wi.waiter.maxDelay.msFormat, KotlinTypes.Time.milliseconds)
3427
}
35-
write("val delay = ExponentialBackoffWithJitter(delayOptions)")
36-
write("")
37-
write("val waiterOptions = StandardRetryStrategyOptions(maxAttempts = 20)")
38-
write("StandardRetryStrategy(waiterOptions, InfiniteTokenBucket, delay)")
3928
}
4029
}
4130

4231
/**
4332
* Renders the client extension methods for a waiter.
4433
*/
4534
internal fun KotlinWriter.renderWaiter(wi: WaiterInfo) {
46-
addImport(
47-
wi.serviceSymbol,
48-
wi.inputSymbol,
49-
wi.outputSymbol,
50-
RuntimeTypes.Core.Retries.Outcome,
51-
RuntimeTypes.Core.Retries.Delay.ExponentialBackoffWithJitterOptions,
52-
RuntimeTypes.Core.Retries.Delay.ExponentialBackoffWithJitter,
53-
RuntimeTypes.Core.Retries.StandardRetryStrategyOptions,
54-
RuntimeTypes.Core.Retries.StandardRetryStrategy,
55-
RuntimeTypes.Core.Retries.Policy.RetryDirective,
56-
RuntimeTypes.Core.Retries.Policy.AcceptorRetryPolicy,
57-
)
58-
5935
write("")
6036
wi.waiter.documentation.ifPresent(::dokka)
6137
withBlock(
62-
"public suspend fun #T.#L(request: #T): Outcome<#T> {",
38+
"public suspend fun #T.#L(request: #T): #T<#T> {",
6339
"}",
6440
wi.serviceSymbol,
6541
wi.methodName,
6642
wi.inputSymbol,
43+
RuntimeTypes.Core.Retries.Outcome,
6744
wi.outputSymbol,
6845
) {
6946
renderRetryStrategy(wi, "strategy")
7047
write("")
7148
renderAcceptorList(wi, "acceptors")
7249
write("")
73-
write("val policy = AcceptorRetryPolicy(request, acceptors)")
50+
write("val policy = #T(request, acceptors)", RuntimeTypes.Core.Retries.Policy.AcceptorRetryPolicy)
7451
write("return strategy.retry(policy) { #L(request) }", wi.opMethodName)
7552
}
7653

7754
write("")
7855
wi.waiter.documentation.ifPresent(this::dokka)
7956
write(
80-
"public suspend fun #T.#L(block: #T.Builder.() -> Unit): Outcome<#T> =",
57+
"public suspend fun #T.#L(block: #T.Builder.() -> Unit): #T<#T> =",
8158
wi.serviceSymbol,
8259
wi.methodName,
8360
wi.inputSymbol,
61+
RuntimeTypes.Core.Retries.Outcome,
8462
wi.outputSymbol,
8563
)
8664
indent()

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ServiceClientConfigGeneratorTest.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ServiceClientConfigGeneratorTest {
4242
contents.assertBalancedBracesAndParens()
4343

4444
val expectedCtor = """
45-
public class Config private constructor(builder: Builder) : HttpAuthConfig, HttpClientConfig, HttpEngineConfig by builder.buildHttpEngineConfig(), IdempotencyTokenConfig, SdkClientConfig, TracingClientConfig {
45+
public class Config private constructor(builder: Builder) : HttpAuthConfig, HttpClientConfig, HttpEngineConfig by builder.buildHttpEngineConfig(), IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TracingClientConfig {
4646
"""
4747
contents.shouldContainWithDiff(expectedCtor)
4848

@@ -54,13 +54,12 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
5454
override val interceptors: kotlin.collections.List<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = builder.interceptors
5555
override val logMode: LogMode = builder.logMode ?: LogMode.Default
5656
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
57-
override val retryStrategy: RetryStrategy = builder.retryStrategy ?: StandardRetryStrategy()
5857
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)
5958
"""
6059
contents.shouldContainWithDiff(expectedProps)
6160

6261
val expectedBuilder = """
63-
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, SdkClientConfig.Builder<Config>, TracingClientConfig.Builder {
62+
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, RetryClientConfig.Builder, RetryStrategyClientConfig.Builder by RetryStrategyClientConfigImpl.BuilderImpl(), SdkClientConfig.Builder<Config>, TracingClientConfig.Builder {
6463
/**
6564
* A reader-friendly name for the client.
6665
*/
@@ -115,12 +114,6 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
115114
*/
116115
override var retryPolicy: RetryPolicy<Any?>? = null
117116
118-
/**
119-
* The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the
120-
* strategy.
121-
*/
122-
override var retryStrategy: RetryStrategy? = null
123-
124117
/**
125118
* The tracer that is responsible for creating trace spans and wiring them up to a tracing backend (e.g.,
126119
* a trace probe). By default, this will create a standard tracer that uses the service name for the root
@@ -255,7 +248,6 @@ public class Config private constructor(builder: Builder) {
255248
override val interceptors: kotlin.collections.List<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = builder.interceptors
256249
override val logMode: LogMode = builder.logMode ?: LogMode.LogRequest
257250
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
258-
override val retryStrategy: RetryStrategy = builder.retryStrategy ?: StandardRetryStrategy()
259251
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)"""
260252
contents.shouldContainWithDiff(expectedConfigValues)
261253
}

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ServiceClientGeneratorTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class ServiceClientGeneratorTest {
7373

7474
@Test
7575
fun `it generates config`() {
76-
val expected = "public class Config private constructor(builder: Builder) : IdempotencyTokenConfig, SdkClientConfig, TracingClientConfig"
76+
val expected = "public class Config private constructor(builder: Builder) : IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TracingClientConfig"
7777
commonTestContents.shouldContainOnlyOnce(expected)
7878
}
7979

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/ServiceWaitersGeneratorTest.kt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,15 @@ class ServiceWaitersGeneratorTest {
7878
@Test
7979
fun testRetryStrategy() {
8080
val expected = """
81-
val strategy = run {
82-
val delayOptions = ExponentialBackoffWithJitterOptions(
83-
initialDelay = 2_000.milliseconds,
84-
scaleFactor = 1.5,
85-
jitter = 1.0,
86-
maxBackoff = 120_000.milliseconds,
87-
)
88-
val delay = ExponentialBackoffWithJitter(delayOptions)
89-
90-
val waiterOptions = StandardRetryStrategyOptions(maxAttempts = 20)
91-
StandardRetryStrategy(waiterOptions, InfiniteTokenBucket, delay)
81+
val strategy = StandardRetryStrategy {
82+
maxAttempts = 20
83+
tokenBucket = InfiniteTokenBucket
84+
delayProvider {
85+
initialDelay = 2_000.milliseconds
86+
scaleFactor = 1.5
87+
jitter = 1.0
88+
maxBackoff = 120_000.milliseconds
89+
}
9290
}
9391
""".formatForTest()
9492
generated.shouldContainOnlyOnce(expected)

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/WaiterGeneratorTest.kt

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,15 @@ class WaiterGeneratorTest {
2828
@Test
2929
fun testDefaultDelays() {
3030
val expected = """
31-
val strategy = run {
32-
val delayOptions = ExponentialBackoffWithJitterOptions(
33-
initialDelay = 2_000.milliseconds,
34-
scaleFactor = 1.5,
35-
jitter = 1.0,
36-
maxBackoff = 120_000.milliseconds,
37-
)
38-
val delay = ExponentialBackoffWithJitter(delayOptions)
39-
40-
val waiterOptions = StandardRetryStrategyOptions(maxAttempts = 20)
41-
StandardRetryStrategy(waiterOptions, InfiniteTokenBucket, delay)
31+
val strategy = StandardRetryStrategy {
32+
maxAttempts = 20
33+
tokenBucket = InfiniteTokenBucket
34+
delayProvider {
35+
initialDelay = 2_000.milliseconds
36+
scaleFactor = 1.5
37+
jitter = 1.0
38+
maxBackoff = 120_000.milliseconds
39+
}
4240
}
4341
""".formatForTest()
4442
generated.shouldContain(expected)
@@ -47,17 +45,15 @@ class WaiterGeneratorTest {
4745
@Test
4846
fun testCustomDelays() {
4947
val expected = """
50-
val strategy = run {
51-
val delayOptions = ExponentialBackoffWithJitterOptions(
52-
initialDelay = 5_000.milliseconds,
53-
scaleFactor = 1.5,
54-
jitter = 1.0,
55-
maxBackoff = 30_000.milliseconds,
56-
)
57-
val delay = ExponentialBackoffWithJitter(delayOptions)
58-
59-
val waiterOptions = StandardRetryStrategyOptions(maxAttempts = 20)
60-
StandardRetryStrategy(waiterOptions, InfiniteTokenBucket, delay)
48+
val strategy = StandardRetryStrategy {
49+
maxAttempts = 20
50+
tokenBucket = InfiniteTokenBucket
51+
delayProvider {
52+
initialDelay = 5_000.milliseconds
53+
scaleFactor = 1.5
54+
jitter = 1.0
55+
maxBackoff = 30_000.milliseconds
56+
}
6157
}
6258
""".formatForTest()
6359
generated.shouldContainOnlyOnce(expected)

0 commit comments

Comments
 (0)