Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep Record of Original Exception in S3 Callbacks #700

Merged
merged 27 commits into from
Oct 25, 2023
Merged

Conversation

waahm7
Copy link
Contributor

@waahm7 waahm7 commented Oct 18, 2023

Issue #, if available:
#697

Description of changes:
Adds a new exception parameter to S3FinishedResponseContext, which can contain the cause of the exception if a Java exception is thrown from the onHeaders or onBody callback.

Some caveats:

  • It only catches exceptions from onHeaders and onBody callbacks
  • Only the last exception is retained.

Example output:

java.util.concurrent.ExecutionException: software.amazon.awssdk.crt.s3.CrtS3RuntimeException: A callback has reported failure. AWS_ERROR_HTTP_CALLBACK_FAILURE(2064): response status code(0). aws error code(), aws error message()

	at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395)
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999)
	at software.amazon.awssdk.crt.test.S3ClientTest.testS3CallbackExceptionIsProperlyPropagated(S3ClientTest.java:352)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: software.amazon.awssdk.crt.s3.CrtS3RuntimeException: A callback has reported failure. AWS_ERROR_HTTP_CALLBACK_FAILURE(2064): response status code(0). aws error code(), aws error message()
	at software.amazon.awssdk.crt.s3.S3FinishedResponseContext.<init>(S3FinishedResponseContext.java:29)
	at software.amazon.awssdk.crt.s3.S3MetaRequestResponseHandlerNativeAdapter.onFinished(S3MetaRequestResponseHandlerNativeAdapter.java:23)
Caused by: java.lang.RuntimeException: Exception From a Java Function
	at software.amazon.awssdk.crt.test.S3ClientTest.testS3CallbackExceptionIsProperlyPropagated(S3ClientTest.java:320)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@waahm7 waahm7 changed the title WIP | Keep Record of Original Exceptions in S3 Callbacks WIP | Keep Record of Original Exception in S3 Callbacks Oct 18, 2023
src/native/s3_client.c Outdated Show resolved Hide resolved
@waahm7 waahm7 requested a review from TingDaoK October 19, 2023 16:11
Copy link
Contributor

@TingDaoK TingDaoK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

src/native/crt.c Outdated
Comment on lines 292 to 294
(*env)->DeleteGlobalRef(env, *out);
if (out != NULL) {
*out = (jthrowable)(*env)->NewGlobalRef(env, (*env)->ExceptionOccurred(env));
Copy link
Contributor

@graebm graebm Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused whether the user is allowed to pass NULL for out?

if they're allowed to pass NULL, then this will crash when you call (*env)->DeleteGlobalRef(env, *out);

if they're not allowed to pass NULL, why is there a if (out != NULL) check?

probably, out should never be NULL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I tried to make it NULL-tolerant but missed the (*env)->DeleteGlobalRef(env, *out);. I have removed the if (out != NULL) condition, since other functions like aws_jni_byte_array_from_cursor are also not NULL-tolerant.

this.errorCode = errorCode;
this.responseStatus = responseStatus;
this.errorPayload = errorPayload;
this.checksumAlgorithm = checksumAlgorithm;
this.didValidateChecksum = didValidateChecksum;
if (cause != null) {
this.exception = new CrtS3RuntimeException(errorCode, responseStatus, errorPayload, cause);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the cause is not null, should we return the cause as-is without wrapping? This will make it eaiser for users to handle the exception. See similar feedback we got before aws/aws-sdk-java-v2#4356

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we want to omit the CRT layer completely from the stack trace, even if the failure was from a Java callback. I do understand the annoyance of nested exceptions, and from a design perspective, we do want to improve error handling in a Java idiomatic way instead of giving access to the CRT error code. See #683 (comment). We should not have exposed the error code directly to begin with, but now we have to maintain backward compatibility.

Debatable: I think it is better for CRT to have a unified exception and the SDK can do exception.getCause() to de-nest it where required or pass S3ClientException as a cause to SDKClientException.

Copy link
Contributor

@graebm graebm Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if Zoe says it's OK to just provide the underlying connection, I'm down with it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed it with @TingDaoK as well, and decided to remove CrtS3RuntimeException since the same data was repeated in two places.

return this.didValidateChecksum;
}

public CrtS3RuntimeException getException() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just make this return the (nullable) cause instead of CrtS3RuntimeException? In our current implementation, the CRT client in the SDK just checks the error code and returns our own SdkClientException and we can't change it now. https://github.com/aws/aws-sdk-java-v2/blob/master/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java#L134

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Java SDK can do exception.errorMessage instead of parsing the errorCode.

@waahm7 waahm7 changed the title WIP | Keep Record of Original Exception in S3 Callbacks Keep Record of Original Exception in S3 Callbacks Oct 24, 2023
@waahm7 waahm7 marked this pull request as ready for review October 24, 2023 17:50
@waahm7 waahm7 enabled auto-merge (squash) October 25, 2023 22:52
@waahm7 waahm7 merged commit 1cb7add into main Oct 25, 2023
38 checks passed
@waahm7 waahm7 deleted the better_exceptions branch October 25, 2023 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The root cause of file system errors is ommitted from SdkClientExceptions
4 participants