-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
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
Avoid socketserver.StreamRequestHandler.wfile doing partial writes #70908
Comments
This is a follow-on from bpo-24291. Currently, for stream servers (as opposed to datagram servers), the wfile attribute is a raw SocketIO object. This means that wfile.write() is a simple wrapper around socket.send(), and can do partial writes. There is a comment inherited from Python 2 that reads: # . . . we make Python 2 only has one kind of “file” object, and it seems partial writes are impossible: <https://docs.python.org/2/library/stdtypes.html#file.write\>. But in Python 3, unbuffered mode means that the lower-level RawIOBase API is involved rather than the higher-level BufferedIOBase API. I propose to change the “wfile” attribute to be a BufferedIOBase object, yet still be “unbuffered”. This could be implemented with a class that looks something like class _SocketWriter(BufferedIOBase):
"""Simple writable BufferedIOBase implementation for a socket
Does not hold data in a buffer, avoiding any need to call flush()."""
def write(self, b):
self._sock.sendall(b)
return len(b) |
Here is a patch with my proposal. |
Hum, since long time ago, Python has issues with partial write. It's hard to guess if a write will always write all data, store the data on partial write, or simply ignore remaining data on partial write. I recall a "write1" function which was well defined: limited to 1 syscall, don't try (or maybe only on the very specific case of EINTR). But I'm not sure that it still exists in the io module of Python 3. asyncio has also issues with the definition of "partial write" in its API. You propose to fix the issue in socketserver. socket.makefile(bufsize=0).write() uses send() and so use partial write. Are you sure that users are prepared for that? Maybe SocketIO must be modified to use sendall() when bufsize=0? |
FYI I recently worked on a issue with partial write in eventlet on Python 3:
By the way, I opened bpo-26292 "Raw I/O writelines() broken for non-blocking I/O" as a follow-up of the eventlet issue. |
Oops, in fact it is read1: |
If a user calls makefile(bufsize=0), they may have written that based on Python 2’s behaviour of always doing full writes. But in Python 3 this is indirectly documented as doing partial writes: makefile() args interpreted as for open, and open() buffering disabled returns a RawIOBase subclass. People porting code from Python 2 may be unprepared for partial writes. Just another subtle Python 2 vs 3 incompatibility. People using only Python 3 might be unprepared if they are not familar with the intricacies of the Python API, but then why are they using bufsize=0? On the other hand, they might require partial writes if they are using select() or similar, so changing it would be a compatibility problem. You could use the same arguments for socketserver. The difference is that wfile being in raw mode is not documented. Almost all of the relevant builtin library code that I reviewed expects full blocking writes, and I did not find any that requires partial writes. So I think making the change in socketserver is less likely to introduce compatibility problems, and is much more beneficial. |
Merged with current code, and copied the original wsgiref test case from bpo-24291, since this patch also fixes that bug. |
Merged with current code, and migrated the interrupted-write test from test_wsgiref into test_socketserver. |
Forgot to remove the workaround added to 3.5 for wsgiref in bpo-24291 |
New changeset 4ea79767ff75 by Martin Panter in branch 'default': |
Committed for 3.6. |
Before this patch, we are using a new thread to host the http server. It works in most cases, but hit the python3.5 partial writes bug (if the script is run by python3.5), which lead to firmware image downloading failure on BMC side. The python3.5 partial writes bug: Issue: python/cpython#70908 Fix: python/cpython@34eeed4#diff-c00d56a0c132ee4bdf79f55a4b43643cf314df1fe122c07c539e120c7ec98b5e That python3.5 bug can be reproduced in a very rigorous env. In our script case, there are two kinds of operations: 1: Script have a thread to host http server, BMC will http download Firmware image from that http server. 2: At the same time, Script will also send redfish(http) request to BMC, to poll the downloading status. These two operations(traffic) need: a: happen at the same time. b: happen within the same process (different thread). For #a: That's why downloading image manually by wget or curl do not reproduce the downloading failure issue For #b: That's how we fix the problem. We host the http server in a new process, to avoid hitting that python3.5 bug.
Before this patch, we are using a new thread to host the http server. It works in most cases, but hit the python3.5 partial writes bug (if the script is run by python3.5), which lead to firmware image downloading failure on BMC side. The python3.5 partial writes bug: Issue: python/cpython#70908 Fix: python/cpython@34eeed4#diff-c00d56a0c132ee4bdf79f55a4b43643cf314df1fe122c07c539e120c7ec98b5e That python3.5 bug can be reproduced in a very rigorous env. In our script case, there are two kinds of operations: 1: Script have a thread to host http server, BMC will http download Firmware image from that http server. 2: At the same time, Script will also send redfish(http) request to BMC, to poll the downloading status. These two operations(traffic) need: a: happen at the same time. b: happen within the same process (different thread). For #a: That's why downloading image manually by wget or curl do not reproduce the downloading failure issue For #b: That's how we fix the problem. We host the http server in a new process, to avoid hitting that python3.5 bug.
Before this patch, we are using a new thread to host the http server. It works in most cases, but hit the python3.5 partial writes bug (if the script is run by python3.5), which lead to firmware image downloading failure on BMC side. The python3.5 partial writes bug: Issue: python/cpython#70908 Fix: python/cpython@34eeed4#diff-c00d56a0c132ee4bdf79f55a4b43643cf314df1fe122c07c539e120c7ec98b5e That python3.5 bug can be reproduced in a very rigorous env. In our script case, there are two kinds of operations: 1: Script have a thread to host http server, BMC will http download Firmware image from that http server. 2: At the same time, Script will also send redfish(http) request to BMC, to poll the downloading status. These two operations(traffic) need: a: happen at the same time. b: happen within the same process (different thread). For #a: That's why downloading image manually by wget or curl do not reproduce the downloading failure issue For #b: That's how we fix the problem. We host the http server in a new process, to avoid hitting that python3.5 bug.
Before this patch, we are using a new thread to host the http server. It works in most cases, but hit the python3.5 partial writes bug (if the script is run by python3.5), which lead to firmware image downloading failure on BMC side. The python3.5 partial writes bug: Issue: python/cpython#70908 Fix: python/cpython@34eeed4#diff-c00d56a0c132ee4bdf79f55a4b43643cf314df1fe122c07c539e120c7ec98b5e That python3.5 bug can be reproduced in a very rigorous env. In our script case, there are two kinds of operations: 1: Script have a thread to host http server, BMC will http download Firmware image from that http server. 2: At the same time, Script will also send redfish(http) request to BMC, to poll the downloading status. These two operations(traffic) need: a: happen at the same time. b: happen within the same process (different thread). For #a: That's why downloading image manually by wget or curl do not reproduce the downloading failure issue For #b: That's how we fix the problem. We host the http server in a new process, to avoid hitting that python3.5 bug.
Before this patch, we are using a new thread to host the http server. It works in most cases, but hit the python3.5 partial writes bug (if the script is run by python3.5), which lead to firmware image downloading failure on BMC side. The python3.5 partial writes bug: Issue: python/cpython#70908 Fix: python/cpython@34eeed4#diff-c00d56a0c132ee4bdf79f55a4b43643cf314df1fe122c07c539e120c7ec98b5e That python3.5 bug can be reproduced in a very rigorous env. In our script case, there are two kinds of operations: 1: Script have a thread to host http server, BMC will http download Firmware image from that http server. 2: At the same time, Script will also send redfish(http) request to BMC, to poll the downloading status. These two operations(traffic) need: a: happen at the same time. b: happen within the same process (different thread). For #a: That's why downloading image manually by wget or curl do not reproduce the downloading failure issue For #b: That's how we fix the problem. We host the http server in a new process, to avoid hitting that python3.5 bug.
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: