-
Notifications
You must be signed in to change notification settings - Fork 9.1k
HADOOP-17812. NPE in S3AInputStream read() after failure to reconnect to store #3222
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
Conversation
@steveloughran Could you help to take a look at this PR? Thx a lot |
When IOException happens during "wrappedStream.read", onReadFailure->reopen will be called and reopen will try to re-open "wrappedStream", but what if exception happens during getting S3Object, then "wrappedStream" will be null, finally, the "retry" may re-execute the read block and cause the NPE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, thank you for not only finding this bug but providing a PR. Always Appreciated.
It took me a while to work out what's gone wrong here. Presumably the connection
to your S3 store was down so long that the re-trying reopen() code failed; this
got as far as the wrapping retry() in read(), which when NPEs.
Although this PR moves the failure from an NPE to IOE, it doesn't do anything
about attempt to recover from the failure, which the retry() code is meant to do
I think the input codestream code needs to simply try and reconnect when
the wrappedStream is null. Either this succeeds (and the subsequent read works) or it fails,
an IOE is raised, and the S3ARetryPolicy decides what to do:
if (wrappedStream == null) {
onReadFailure(e, 1, false);
}
read(...)
other points
Testing
See Testing S3A policy
you have to declare the S3 endpoint you ran the entire hadoop- suite against. If you've an internal endpoint, that's fine -just need that test coverage.
Yetus/Jenkins can't do it.
We would also like to have a test for the new problem; TestS3AInputStreamRetry is where it should go
MultiFileCloudParquetPartitionReader
Can you point me at at MultiFileCloudParquetPartitionReader, I would really like to see it
If you are targeting Hadoop 3.3.1+, there are likely opportunities for speedups and statisics collection/reporting.
I've been playihg a bit myself there to see what could be done
Ths @steveloughran for the reviewing, for the code suggestion you gave is the same as the PR try {
// When exception happens before re-setting wrappedStream in "reopen" called
// by onReadFailure, then wrappedStream will be null. But the **retry** may
// re-execute this block and cause NPE if we don't check wrappedStream
if (wrappedStream == null) {
throw new IOException("Null IO stream for reading " + uri);
}
b = wrappedStream.read();
} catch (EOFException e) {
return -1;
} catch (SocketTimeoutException e) {
onReadFailure(e, 1, true);
throw e;
} catch (IOException e) {
onReadFailure(e, 1, false);
throw e;
} when wrappedStream is null, the IOException is thrown, then the catch block will call onReadFailure to retry. |
update 2021-07-23-20:49 actually, I may be wrong here. will need to write the test to see what the outcome is. still be best to preserve whatever did fail though
yes, but the exception raised is an IOE, not the underlying cause, so the retry logic won't examine failure, it will simply give up. If you look at the S3ARetryPolicy you can see how no attempt is made to retry a generic IOE. Therefore there will be precisely one retry attempt (the exception handler), and if that doesn't fix it (e.g. server not yet recovered): Failure.
I am proposing that on the entry to method, the full attempt to reconnect is made if to the stream is null In the test which this PR is going to need, the issue will become apparent if the simulated failure is a sequence of
With ConnectTimeoutException being raised on the reconnect, the retry will try to connect with backoff, jitter, configurable limit. Throwing a simple IOE will fail on the first retry (test case should also setup a retry policy with a retry interval of 0ms so it doesn't trigger any delays) |
I modified the unit tests which can cover the NPE described in the JIRA, and I ran the Integration tests and some tests failed. I don't know if the failed unit tests are expected. I updated the test result in https://issues.apache.org/jira/browse/HADOOP-17812?focusedCommentId=17386993&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-17386993 I still think throwing exception when wrappedStream equals null may be better. If we use try {
if (wrappedStream == null) {
onReadFailure(null, 1, false);
}
b = wrappedStream.read();
} catch (EOFException e) {
return -1;
} catch (SocketTimeoutException e) {
onReadFailure(e, 1, true);
throw e;
} catch (IOException e) {
onReadFailure(e, 1, false);
throw e;
} if onReadFailure called when detecting wrappedStream==null is failed, then the onReadFailure will be called again in the block of |
I'm thinking:
if (wrappedStream == null) {
// trigger a re-open.
reopen("failure recovery", getPos(), 1, false);
}
try {
b = wrappedStream.read();
} catch (EOFException e) {
return -1;
} catch (SocketTimeoutException e) {
onReadFailure(e, 1, true);
throw e;
} catch (IOException e) {
onReadFailure(e, 1, false);
throw e;
} Critically, onReadFailure will stop trying to reopen the stream, instead it release (or, socket exception: force breaks) the stream. Removes
so:
Result:
|
@steveloughran That's really a smart suggestion. I've changed it accordingly. Thx for the review. BTW, I will upload the integration test results in JIRA later. Thx again. Have a good day! |
bq. BTW, I will upload the integration test results in JIRA later. don't need the full results, just add a comment here about where you tested (e.g. #3240 (comment) ) and #3240 (comment) ) Regarding those failures -your AWS credentials are for some private store aren't they? So all tests referencing landsat data are failing as you are not authed by AWS. The testing,md doc shows how you can switch to a different large file in your own store, after which it turns off a set of tests which won't be valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
patch LGTM. I suspect the original reconnect logic dates from before all the Invoker.retry() stuff went in, we never really looked at it in full detail to see whether it needed attention. You know how it is: if things seem to work, leave them alone...
add your test setup in a comment and I'll do the merge
Thx @steveloughran, I re-tested the integration test in AWS. and the result can be found at https://issues.apache.org/jira/browse/HADOOP-17812?focusedCommentId=17389239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-17389239 Tested |
+1, merged to trunk. Thank you for this Bobby -can you cherrypick the test from trunk, apply to branch-3.3 and retest? I just need to know that it works. There'll be no extra review unless there are merge problems. There would be major merge pain going back to 3.2; I'd like to avoid that. If you do want to go that way, it would be a separate patch, and we'd skip the test changes because that is so radically different |
… to store (apache#3222) This improves error handling after multiple failures reading data -when the read fails and attempts to reconnect() also fail. Contributed by Bobby Wang.
… to store (#3222) This improves error handling after multiple failures reading data -when the read fails and attempts to reconnect() also fail. Contributed by Bobby Wang. Change-Id: If17dee395ad6b9b7c738021bad20d0a13eb4011e
… to store (apache#3222) This improves error handling after multiple failures reading data -when the read fails and attempts to reconnect() also fail. Contributed by Bobby Wang.
…to reconnect to store (apache#3222) This improves error handling after multiple failures reading data -when the read fails and attempts to reconnect() also fail. Contributed by Bobby Wang. Change-Id: If17dee395ad6b9b7c738021bad20d0a13eb4011e
When IOException happens during "wrappedStream.read", onReadFailure->reopen
will be called and reopen will try to re-open "wrappedStream", but what if
exception happens during getting S3Object, then "wrappedStream" will be null,
finally, the "retry" may re-execute the read block and cause the NPE.
See the issue https://issues.apache.org/jira/browse/HADOOP-17812