[
https://issues.apache.org/jira/browse/HDFS-7163?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Eric Payne updated HDFS-7163:
-----------------------------
Attachment: HDFS-7163.005.patch
[~daryn], thank you very much for your in-depth analysis and helpful comments!
{quote}
Code:
1. We may want to defer the open until a read occurs.
{quote}
I have done that in this patch, but it caused some of the unit tests to fail
because they were expecting the input stream to be open after the {{fs.open()}}
call. I had to change these tests:
- {{FSXAttrBaseTest}}
- {{TestAuditLogs}}
- {{TestWebHdfsFileSystemContract}}
- {{TestWebHdfsTokens}}
My question is, are we comfortable that nothing is depending on the current
behavior?
bq. 2. {{runnerState}} ... should just be initialized as DISCONNECTED.
Done.
bq. 3. If read(...) throws an IOE due to an explicitly closed stream, will
retries occur?
No. The check for the explicitly closed state happens outside of the retry
logic.
bq. 4. In {{connect(URL)}}, Calling it {{cachedConnection}} would clarify its
purpose.
Done.
{quote}
5. In getResponse:
5.1. Should {{initializeInputStream}} be unconditionally invoked inside the
prior null check on connection? Ie. Is there ever a case when it shouldn't be
initialized when a new connection is made?
5.2. I think the logic should be if (conn != cachedConnection) {
cachedConnection = conn; in = initializeInputStream(cachedConnection) }
{quote}
If the connection is not cached, initialization always needs to happen.
However, the converse is not true. That is, even if connection is cached,
initialization still may need to happen.
For a seek, the connection is cached into {{cachedConnection}} by
{{ReadRunner#read}} after invoking the {{URLRunner}} to make the connection.
The {{URLRunner}} is used rather than the {{ReadRunner}} so that
{{AbstractRunner#connect}} can be told that the URL has already been
redirected. On the ohter hand, for a regular read (non-seek case), the
{{ReadRunner#connect}} makes the connection, but {{cachedConnection}} isn't
cached until {{eadRunner#getResponse}} because we want {{validateResponse}} to
be run before caching the connection.
So, in {{ReadRunner#getResponse}}, in the seek case, {{cachedConnection}} will
be non-null, but the input stream ({{in}}) will be null. In the regular read
case, both will be null.
So, I took out the check for if {{cachedConnection}} is null and always cache
it, but I kept the check for if {{in}} is null. I realize that
{{cachedConnection}} doesn't always need to be cached, but the performance cost
is small and it makes the code cleaner.
bq. 5.3. Should use URL#getAuthority instead of explicitly extracting and
joining the host and port.
Done.
bq. 6. In ReadRunner#initializeInputStream has a misspelled "performznt".
Done
bq. 7. In {{closeInputStream}}, I'd use {{IOUtils.closeStream}} to ensure the
close doesn't throw which would prevent the stream state from being updated.
I replaced {{in.close()}} with {{IOUtils.close(cachedConnection)}}. Is that
what you meant?
bq. 8. In general the state management isn't clear. DISCONNECTED vs SEEK appear
to be the same, with the exception that SEEK allows the connection to be
reopened. When errors occur and the stream is DISCONNECTED, are you sure it
will retry/recover in all cases?
I've done quite a bit of manual testing in a full cluster with reasonably
substantial files (16GB). Can you be more specific about your concerns?
As far as each state is concerned, SEEK and DISCONNECTED are a little different
than your comment. Let me try to explain in a little more detail
- DISCONNECTED
Connection is closed programmatically by ReadRunner after an exception has
occurred. {{ReadRunner}} will attempt to open a new connection if it is retried
while in this state.
- OPEN
Connection has been successfully established by {{ReadRunner}}. This occurs
after the input stream has been initialized.
- SEEK
{{ReadRunner}} will only be put in this state if the user code has explicitly
called seek(). {{ReadRunner}} will use this state as a trigger to perform a
redirected connection (as I have discussed above in my reply to your point,
5.1). Once the connection is established and the input stream is initialized,
the {{RunnerState}} will move to OPEN. Retries will not be attempted while in
this state. If an IOException occurs while {{URLRunner}} is attempting to open
a redirected connection, {{ReadRunner}} will move to the DISCOMMECTED state and
retry via the normal read path.
- CLOSED
{{ReadRunner}} is put in this state when user code has explicitly called
close().
Also, as part of this patch, I added a {{RunnerState}} parameter to the
{{closeInputStream}} method. These two are not necessarily tied together, but
it does make it clearer (at least in my mind) which state {{ReadRunner}} will
be moving to as a result of the action. If you are uncomfortable with that, I
can separate them out.
{quote}
Tests:
1. In testWebHdfsReadRetries
1.1. A 5m timeout seems overly generous for something that will hopefully
fail much faster if there's a problem.
{quote}
Changed to 90 seconds.
bq. 1.2. Why the 5s safemode extension? Seems like it will unnecessarily
slow down the test?
I think that was just from code I copied from the {{MiniDfsCluster}} test. I
removed it.
bq. 3.The healthy check on dfs appears redundant since cluster#waitActive()
already checked.
I removed it.
bq. 2. The client shouldn't just give up and do nothing for InvalidToken. It's
supposed to try and get another token and retry the read. It's unclear if that
actually happens or if the position is retaining correctly?
In a unit test, the client does nothing for InvalidToken exception because
security is turned off. I updated the test to mock a token being replaced and
then verified that retries happened.
Kind of a side note: this had to be verified outside of the retry policy
verification that I already had mocked up. {{runWithRetry}} doesn't use the
retry policy when handling {{InvalidToken}} exceptions. Instead, it tries to
replace the token and, if it succeeds, retries the connection. {{runWithRetry}}
will retry as many times as that process succeeds with no upper limit. So, I
mocked replacing the token twice and then verified that
{{ReadRunner#getResponse}} was called 3 times.
bq. 1.3. May consider more mockito verifies.
TestWebHDFS has been enhanced.
> WebHdfsFileSystem should retry reads according to the configured retry policy.
> ------------------------------------------------------------------------------
>
> Key: HDFS-7163
> URL: https://issues.apache.org/jira/browse/HDFS-7163
> Project: Hadoop HDFS
> Issue Type: Bug
> Components: webhdfs
> Affects Versions: 3.0.0, 2.5.1
> Reporter: Eric Payne
> Assignee: Eric Payne
> Attachments: HDFS-7163-branch-2.003.patch,
> HDFS-7163-branch-2.004.patch, HDFS-7163-branch-2.7.003.patch,
> HDFS-7163-branch-2.7.004.patch, HDFS-7163.001.patch, HDFS-7163.002.patch,
> HDFS-7163.003.patch, HDFS-7163.004.patch, HDFS-7163.005.patch, WebHDFS Read
> Retry.pdf
>
>
> In the current implementation of WebHdfsFileSystem, opens are retried
> according to the configured retry policy, but not reads. Therefore, if a
> connection goes down while data is being read, the read will fail and the
> read will have to be retried by the client code.
> Also, after a connection has been established, the next read (or seek/read)
> will fail and the read will have to be restarted by the client code.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)