[ 
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)

Reply via email to