-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
aiohttp client corrupts uploads that use chunked encoding due to race condition in socket reuse #3281
Comments
Looks like a real bug. |
I think the task should be terminated on error or return to the connection pool (close()/release() methods). Consider the following scenario:
|
I started looking at what's happening. It appears as though cancel is called on the writer task but it's either not cancelling or the actual cancel is delayed. Regardless, do you know how to properly cancel a chunked upload? Normally a 0 length chunk indicates the upload is complete. Do we need to kill off the chunk upload and insert a 0-length chunk so the server knows to "release" the socket for another request? |
I may be over-engineering a solution to this problem but, assuming we need to send a 0-length chunk to close out the connection, I think 5 functions are going to have to change from being synchronous to asynchronous. The gist is that nothing awaits the cancellation of ClientResponse._writer in ClientResponse._cleanup_writer. If we make ClientResponse._cleanup_writer async to wait for a positive ACK on the cancellation, that means that ClientResponse.release, ClientResponse.close, ClientResponse._response_eof, and thus StreamReader.on_eof all have to go async. Are you willing to support changing the 3 public functions to async? |
I don't think you would want the client to automatically send the 0-length chunk on "cancel" - some servers might interpret it as a successfully completed upload. In the error case, a server has to read the body of the http request if it wants to re-use the connection, but because of the vaguaries of clients it's best to throw the connection away. Is this bug still blocked? If a transfer was cancelled pre-maturely (i.e. expect 100 continue returns error) and the http contract is broken aiohttp shouldn't put the connection back into the pool. Cancelling the writer task sounds reaosnable. @asvetlov what would that look like? |
I have a feeling this may have been fixed. If not, someone needs to provide a reproducer/test. |
Long story short
It appears that the aiohttp client will corrupt chunked uploads while under stress due to a race condition between an upload finishing and reusing that TCP socket
Expected behaviour
Chunked uploads should work without issues
Actual behaviour
The problem manifested by my HTTP server complaining that the first line of a HTTP request (e.g. PUT /whatever) was an incorrect HTTP chunk size. Upon further investigation with Wireshark, a normal chunked HTTP upload randomly got a new HTTP request inserted mid stream. The server therefore expected to read the chunk size but instead encountered an HTTP header.
I dug through the code and I believe the issue is the following:
My workaround is currently to have the server unnecessarily read the whole request before sending back the HTTP status code.
Steps to reproduce
On the server side, simply start an aiohttp HTTP server with 0 routes added. Create a client that attempts to upload several thousand files at once using a HTTP POST or PUT using chunked encoding.
Your environment
CentOS 7 with manually compiled Python 3.7.0 and aiohttp 3.4.4 (client).
The text was updated successfully, but these errors were encountered: