First comment: In the I/O library, there is documented behavior for how things work in the presence of non-blocking I/O. For example, read/write methods returning None on raw file objects. Methods on BufferedIO instances raise a BlockingIOError for operations that can't complete.
However, the implementation of close() is currently broken. If buffered I/O is being used and a file is closed, it's possible that the close will fail due to a BlockingIOError occurring as buffered data is flushed to output. However, in this case, the file is closed anyways and there is no possibility to retry. Here is an example to illustrate:
>>> from socket import *
>>> s = socket(AF_INET, SOCK_STREAM)
>>> s.connect(('somehost', port))
>>> s.setblocking(False)
>>> f = s.makefile('wb', buffering=10000000) # Large buffer
>>> f.write(b'x'*1000000)
>>>
Now, watch carefully
>>> f
<_io.BufferedWriter name=4>
>>> f.closed
False
>>> f.close()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
BlockingIOError: [Errno 35] write could not complete without blocking
>>> f
<_io.BufferedWriter name=-1>
>>> f.closed
True
>>>
I believe this can be fixed by changing a single line in Modules/_io/bufferedio.c:
--- bufferedio_orig.c 2015-10-25 16:40:22.000000000 -0500
+++ bufferedio.c 2015-10-25 16:40:35.000000000 -0500
@@ -530,10 +530,10 @@
res = PyObject_CallMethodObjArgs((PyObject *)self, _PyIO_str_flush, NULL);
if (!ENTER_BUFFERED(self))
return NULL;
- if (res == NULL)
- PyErr_Fetch(&exc, &val, &tb);
- else
- Py_DECREF(res);
+ if (res == NULL)
+ goto end;
+ else
+ Py_DECREF(res);
res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_close, NULL);
With this patch, the close() method can be retried as appropriate until all buffered data is successfully written.
|