Skip to content

Commit ead3b78

Browse files
committed
Add request level override config overload
Fixed a bug where an exception thrown in waiterAcceptor could cause the async waiter operations to hang Add request override config overload in Waiter
1 parent 45fa52c commit ead3b78

File tree

22 files changed

+626
-110
lines changed

22 files changed

+626
-110
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/docs/WaiterDocs.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ public static CodeBlock waiterOperationConsumerBuilderJavadoc(ClassName clientCl
6666
+ "<p>This is a convenience method to create an instance of "
6767
+ "the request builder without the need "
6868
+ "to create one manually using {@link $T.builder()} ")
69-
.param(operationModel.getInput().getVariableName(), "the request to be used"
69+
.param(operationModel.getInput().getVariableName(), "The consumer that will"
70+
+ " configure the "
71+
+ "request to be used"
7072
+ " for polling")
7173
.returns(clientClassName.simpleName().contains("Async") ?
7274
"CompletableFuture of the WaiterResponse containing either a "
@@ -79,7 +81,6 @@ public static CodeBlock waiterOperationConsumerBuilderJavadoc(ClassName clientCl
7981
.add(javadocs, clientClassName, operationModel.getMethodName(), waiterDefinition.getKey(),
8082
requestClassName)
8183
.build();
82-
8384
}
8485

8586
public static CodeBlock waiterBuilderMethodJavadoc(ClassName className) {
@@ -170,4 +171,49 @@ public static CodeBlock waiterBuilderBuildJavadoc(ClassName className) {
170171
.add(javadocs, className, className)
171172
.build();
172173
}
174+
175+
public static CodeBlock waiterOperationWithOverrideConfigConsumerBuilder(ClassName clientClassName,
176+
ClassName requestClassName,
177+
Map.Entry<String, WaiterDefinition> waiterDefinition,
178+
OperationModel opModel) {
179+
String javadocs = new DocumentationBuilder().description("Polls {@link $T#$N} API until the desired condition "
180+
+ "{@code $N} is met, "
181+
+ "or until it is determined that the resource will never "
182+
+ "enter into the desired state. \n "
183+
+ "<p>This is a convenience method to create an instance of "
184+
+ "the request builder and instance of the override config "
185+
+ "builder")
186+
.param(opModel.getInput().getVariableName(),
187+
"The consumer that will configure the request to be used for polling")
188+
.param("overrideConfig",
189+
"The consumer that will configure the per request override "
190+
+ "configuration for waiters")
191+
.returns("WaiterResponse containing either a response or an exception that "
192+
+ "has matched with the waiter success condition")
193+
.build();
194+
return CodeBlock.builder()
195+
.add(javadocs, clientClassName, opModel.getMethodName(), waiterDefinition.getKey())
196+
.build();
197+
198+
}
199+
200+
public static CodeBlock waiterOperationWithOverrideConfig(ClassName clientClassName,
201+
Map.Entry<String, WaiterDefinition> waiterDefinition,
202+
OperationModel opModel) {
203+
String javadocs = new DocumentationBuilder().description("Polls {@link $T#$N} API until the desired condition "
204+
+ "{@code $N} is met, "
205+
+ "or until it is determined that the resource will never "
206+
+ "enter into the desired state")
207+
.param(opModel.getInput().getVariableName(), "The request to be"
208+
+ " used"
209+
+ " for polling")
210+
.param("overrideConfig", "Per request "
211+
+ "override configuration for waiters")
212+
.returns("WaiterResponse containing either a response or an exception that "
213+
+ "has matched with the waiter success condition")
214+
.build();
215+
return CodeBlock.builder()
216+
.add(javadocs, clientClassName, opModel.getMethodName(), waiterDefinition.getKey())
217+
.build();
218+
}
173219
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/waiters/BaseWaiterClassSpec.java

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import software.amazon.awssdk.codegen.poet.PoetUtils;
5757
import software.amazon.awssdk.core.ApiName;
5858
import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
59+
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
5960
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
6061
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
6162
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
@@ -102,6 +103,7 @@ public TypeSpec poetSpec() {
102103
typeSpecBuilder.addMethod(staticErrorCodeMethod());
103104
typeSpecBuilder.addMethods(waiterOperations());
104105
typeSpecBuilder.addMethods(waiterAcceptorInitializers());
106+
typeSpecBuilder.addMethods(waiterConfigInitializers());
105107
typeSpecBuilder.addFields(waitersFields());
106108
additionalTypeSpecModification(typeSpecBuilder);
107109

@@ -173,31 +175,55 @@ private MethodSpec constructor() {
173175
return ctor.build();
174176
}
175177

178+
private List<MethodSpec> waiterConfigInitializers() {
179+
List<MethodSpec> initializers = new ArrayList<>();
180+
waiters.forEach((k, v) -> initializers.add(waiterConfigInitializer(k, v)));
181+
return initializers;
182+
}
183+
184+
private MethodSpec waiterConfigInitializer(String waiterKey, WaiterDefinition waiterDefinition) {
185+
ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
186+
MethodSpec.Builder configMethod =
187+
MethodSpec.methodBuilder(waiterFieldName(waiterKey) + "Config")
188+
.addModifiers(PRIVATE, STATIC)
189+
.addParameter(overrideConfig, "overrideConfig")
190+
.returns(overrideConfig);
191+
192+
configMethod.addStatement("$T<$T> optionalOverrideConfig = Optional.ofNullable(overrideConfig)",
193+
Optional.class,
194+
WaiterOverrideConfiguration.class);
195+
configMethod.addStatement("int maxAttempts = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::maxAttempts)"
196+
+ ".orElse($L)",
197+
waiterDefinition.getMaxAttempts());
198+
configMethod.addStatement("$T backoffStrategy = optionalOverrideConfig."
199+
+ "flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse($T.create($T.ofSeconds($L)))",
200+
BackoffStrategy.class,
201+
FixedDelayBackoffStrategy.class,
202+
Duration.class,
203+
waiterDefinition.getDelay());
204+
configMethod.addStatement("$T waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout)"
205+
+ ".orElse(null)",
206+
Duration.class);
207+
208+
configMethod.addStatement("return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy"
209+
+ "(backoffStrategy).waitTimeout(waitTimeout).build()");
210+
return configMethod.build();
211+
}
212+
176213
private CodeBlock waiterFieldInitialization(Map.Entry<String, WaiterDefinition> waiterDefinition) {
177214
String waiterKey = waiterDefinition.getKey();
178-
String waiterName = lowercaseFirstChar(waiterKey);
179215
WaiterDefinition waiter = waiterDefinition.getValue();
180-
String overrideConfigurationVarName = waiterName + "Strategy";
181216
OperationModel opModel = operationModel(waiter);
182217
CodeBlock.Builder codeBlockBuilder = CodeBlock
183-
.builder()
184-
.addStatement("$T $N = builder.overrideConfiguration == null ? $T.builder().maxAttempts($L)"
185-
+ ".backoffStrategy($T.create($T.ofSeconds($L))).build() : builder.overrideConfiguration",
186-
WaiterOverrideConfiguration.class,
187-
overrideConfigurationVarName,
188-
WaiterOverrideConfiguration.class,
189-
waiter.getMaxAttempts(),
190-
FixedDelayBackoffStrategy.class,
191-
Duration.class,
192-
waiter.getDelay());
193-
218+
.builder();
194219

195220
String waiterFieldName = waiterFieldName(waiterKey);
196-
codeBlockBuilder.add("this.$L = $T.builder($T.class).overrideConfiguration($L).acceptors($LAcceptors())",
221+
codeBlockBuilder.add("this.$L = $T.builder($T.class)"
222+
+ ".acceptors($LAcceptors()).overrideConfiguration($LConfig(builder.overrideConfiguration))",
197223
waiterFieldName,
198224
waiterClassName,
199225
ClassName.get(modelPackage, opModel.getReturnType().getReturnType()),
200-
overrideConfigurationVarName,
226+
waiterFieldName,
201227
waiterFieldName);
202228

203229
additionalWaiterConfig().ifPresent(codeBlockBuilder::add);
@@ -275,9 +301,33 @@ private List<MethodSpec> waiterOperations() {
275301
private Stream<MethodSpec> waiterOperations(Map.Entry<String, WaiterDefinition> waiterDefinition) {
276302
List<MethodSpec> methods = new ArrayList<>();
277303
methods.add(waiterOperation(waiterDefinition));
304+
methods.add(waiterOperationWithOverrideConfig(waiterDefinition));
278305
return methods.stream();
279306
}
280307

308+
private MethodSpec waiterOperationWithOverrideConfig(Map.Entry<String, WaiterDefinition> waiterDefinition) {
309+
String waiterMethodName = waiterDefinition.getKey();
310+
OperationModel opModel = operationModel(waiterDefinition.getValue());
311+
312+
ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
313+
ClassName requestType = ClassName.get(modelPackage, opModel.getInput().getVariableType());
314+
315+
String waiterFieldName = waiterFieldName(waiterDefinition.getKey());
316+
MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
317+
.addParameter(requestType, opModel.getInput().getVariableName())
318+
.addParameter(overrideConfig, "overrideConfig")
319+
.addModifiers(PUBLIC)
320+
.addAnnotation(Override.class)
321+
.addStatement("return $L.$L(() -> client.$N(applyWaitersUserAgent($N)), $LConfig(overrideConfig))",
322+
waiterFieldName,
323+
waiterClassName.simpleName().equals("Waiter") ? "run" : "runAsync",
324+
lowercaseFirstChar(waiterDefinition.getValue().getOperation()),
325+
opModel.getInput().getVariableName(),
326+
waiterFieldName);
327+
328+
return builder.build();
329+
}
330+
281331
private MethodSpec waiterOperation(Map.Entry<String, WaiterDefinition> waiterDefinition) {
282332
String waiterMethodName = waiterDefinition.getKey();
283333
OperationModel opModel = operationModel(waiterDefinition.getValue());

codegen/src/main/java/software/amazon/awssdk/codegen/poet/waiters/BaseWaiterInterfaceSpec.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ private Stream<MethodSpec> waiterOperations(Map.Entry<String, WaiterDefinition>
102102
List<MethodSpec> methods = new ArrayList<>();
103103
methods.add(waiterOperation(waiterDefinition));
104104
methods.add(waiterConsumerBuilderOperation(waiterDefinition));
105+
methods.add(waiterOperationWithOverrideConfig(waiterDefinition));
106+
methods.add(waiterConsumerBuilderOperationWithOverrideConfig(waiterDefinition));
105107
return methods.stream();
106108
}
107109

@@ -118,6 +120,54 @@ private MethodSpec waiterOperation(Map.Entry<String, WaiterDefinition> waiterDef
118120
return unsupportedOperation(builder).build();
119121
}
120122

123+
private MethodSpec waiterOperationWithOverrideConfig(Map.Entry<String, WaiterDefinition> waiterDefinition) {
124+
String waiterMethodName = waiterDefinition.getKey();
125+
OperationModel opModel = model.getOperation(waiterDefinition.getValue().getOperation());
126+
ClassName requestClass = ClassName.get(modelPackage,
127+
opModel.getInput().getVariableType());
128+
CodeBlock javadoc = WaiterDocs.waiterOperationWithOverrideConfig(
129+
clientClassName(), waiterDefinition, opModel);
130+
131+
MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
132+
.addParameter(requestClass, opModel.getInput().getVariableName())
133+
.addParameter(ClassName.get(WaiterOverrideConfiguration.class), "overrideConfig")
134+
.addJavadoc(javadoc);
135+
return unsupportedOperation(builder).build();
136+
}
137+
138+
private MethodSpec waiterConsumerBuilderOperationWithOverrideConfig(Map.Entry<String, WaiterDefinition> waiterDefinition) {
139+
String waiterMethodName = waiterDefinition.getKey();
140+
OperationModel opModel = model.getOperation(waiterDefinition.getValue().getOperation());
141+
ClassName requestClass = ClassName.get(modelPackage,
142+
opModel.getInput().getVariableType());
143+
ParameterizedTypeName requestType = ParameterizedTypeName.get(ClassName.get(Consumer.class),
144+
requestClass.nestedClass("Builder"));
145+
ParameterizedTypeName overrideConfigType =
146+
ParameterizedTypeName.get(ClassName.get(Consumer.class),
147+
ClassName.get(WaiterOverrideConfiguration.class).nestedClass("Builder"));
148+
149+
CodeBlock javadoc = WaiterDocs.waiterOperationWithOverrideConfigConsumerBuilder(
150+
clientClassName(), requestClass, waiterDefinition, opModel);
151+
152+
String inputVariable = opModel.getInput().getVariableName();
153+
154+
MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
155+
.addParameter(requestType, inputVariable)
156+
.addParameter(overrideConfigType, "overrideConfig")
157+
.addJavadoc(javadoc);
158+
159+
builder.addModifiers(Modifier.DEFAULT, Modifier.PUBLIC)
160+
.addStatement("return $L($T.builder().applyMutation($L).build(),"
161+
+ "$T.builder().applyMutation($L).build())",
162+
getWaiterMethodName(waiterMethodName),
163+
requestClass,
164+
inputVariable,
165+
ClassName.get(WaiterOverrideConfiguration.class),
166+
"overrideConfig");
167+
168+
return builder.build();
169+
}
170+
121171
private MethodSpec waiterConsumerBuilderOperation(Map.Entry<String, WaiterDefinition> waiterDefinition) {
122172
String waiterMethodName = waiterDefinition.getKey();
123173
OperationModel opModel = model.getOperation(waiterDefinition.getValue().getOperation());

codegen/src/main/resources/software/amazon/awssdk/codegen/waiters/WaitersRuntime.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,7 @@ private WaitersRuntime() {
3333
}
3434

3535
private static List<WaiterAcceptor<Object>> defaultAcceptors() {
36-
return Arrays.asList(throwOnUnmatchedExceptionWaiter(), retryOnUnmatchedResponseWaiter());
37-
}
38-
39-
private static WaiterAcceptor<Object> throwOnUnmatchedExceptionWaiter() {
40-
return WaiterAcceptor.errorOnExceptionAcceptor(t -> {
41-
if (t instanceof RuntimeException) {
42-
throw (RuntimeException) t;
43-
}
44-
45-
throw SdkClientException.create("Encountered unexpected exception.", t);
46-
});
36+
return Collections.singletonList(retryOnUnmatchedResponseWaiter());
4737
}
4838

4939
private static WaiterAcceptor<Object> retryOnUnmatchedResponseWaiter() {
@@ -502,7 +492,8 @@ public WaiterState waiterState() {
502492

503493
@Override
504494
public boolean matches(SdkResponse response) {
505-
return response.sdkHttpResponse().statusCode() == statusCode;
495+
return response.sdkHttpResponse() != null &&
496+
response.sdkHttpResponse().statusCode() == statusCode;
506497
}
507498

508499
@Override

0 commit comments

Comments
 (0)