summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorNeil Schemenauer <nas-github@arctrix.com>2017-09-22 17:17:30 (GMT)
committerGitHub <noreply@github.com>2017-09-22 17:17:30 (GMT)
commit0a1ff24acfc15d8c7f2dc41000a6f3d9a31e7480 (patch)
tree6589151fac1f3544649dbd9a071ac9e302062c6f /Modules
parentda9b4cfb488119f2493a762fcb1d85c58494f51d (diff)
downloadcpython-0a1ff24acfc15d8c7f2dc41000a6f3d9a31e7480.zip
cpython-0a1ff24acfc15d8c7f2dc41000a6f3d9a31e7480.tar.gz
cpython-0a1ff24acfc15d8c7f2dc41000a6f3d9a31e7480.tar.bz2
bpo-17852: Maintain a list of BufferedWriter objects. Flush them on exit. (#3372)
* Maintain a list of BufferedWriter objects. Flush them on exit. In Python 3, the buffer and the underlying file object are separate and so the order in which objects are finalized matters. This is unlike Python 2 where the file and buffer were a single object and finalization was done for both at the same time. In Python 3, if the file is finalized and closed before the buffer then the data in the buffer is lost. This change adds a doubly linked list of open file buffers. An atexit hook ensures they are flushed before proceeding with interpreter shutdown. This is addition does not remove the need to properly close files as there are other reasons why buffered data could get lost during finalization. Initial patch by Armin Rigo. * Use weakref.WeakSet instead of WeakKeyDictionary. * Simplify buffered double-linked list types. * In _flush_all_writers(), suppress errors from flush(). * Remove NEWS entry, use blurb. * Take more care when flushing file buffers from atexit. The previous implementation was not careful enough to avoid causing issues in multi-threaded cases. Check for buf->ok and buf->finalizing before actually doing the flush. Also, increase the refcnt to ensure the object does not disappear.
Diffstat (limited to 'Modules')
-rw-r--r--Modules/_io/_iomodule.c2
-rw-r--r--Modules/_io/_iomodule.h2
-rw-r--r--Modules/_io/bufferedio.c51
3 files changed, 54 insertions, 1 deletions
diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c
index f0621f4..5db44f9 100644
--- a/Modules/_io/_iomodule.c
+++ b/Modules/_io/_iomodule.c
@@ -766,6 +766,8 @@ PyInit__io(void)
!(_PyIO_empty_bytes = PyBytes_FromStringAndSize(NULL, 0)))
goto fail;
+ _Py_PyAtExit(_PyIO_atexit_flush);
+
state->initialized = 1;
return m;
diff --git a/Modules/_io/_iomodule.h b/Modules/_io/_iomodule.h
index db84037..1dce5da 100644
--- a/Modules/_io/_iomodule.h
+++ b/Modules/_io/_iomodule.h
@@ -183,3 +183,5 @@ extern PyObject *_PyIO_empty_str;
extern PyObject *_PyIO_empty_bytes;
extern PyTypeObject _PyBytesIOBuffer_Type;
+
+extern void _PyIO_atexit_flush(void);
diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c
index b2b9ade..edc4ba5 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -197,7 +197,7 @@ bufferediobase_write(PyObject *self, PyObject *args)
}
-typedef struct {
+typedef struct _buffered {
PyObject_HEAD
PyObject *raw;
@@ -239,8 +239,18 @@ typedef struct {
PyObject *dict;
PyObject *weakreflist;
+
+ /* a doubly-linked chained list of "buffered" objects that need to
+ be flushed when the process exits */
+ struct _buffered *next, *prev;
} buffered;
+/* the actual list of buffered objects */
+static buffered buffer_list_end = {
+ .next = &buffer_list_end,
+ .prev = &buffer_list_end
+};
+
/*
Implementation notes:
@@ -379,9 +389,20 @@ _enter_buffered_busy(buffered *self)
static void
+remove_from_linked_list(buffered *self)
+{
+ self->next->prev = self->prev;
+ self->prev->next = self->next;
+ self->prev = NULL;
+ self->next = NULL;
+}
+
+static void
buffered_dealloc(buffered *self)
{
self->finalizing = 1;
+ if (self->next != NULL)
+ remove_from_linked_list(self);
if (_PyIOBase_finalize((PyObject *) self) < 0)
return;
_PyObject_GC_UNTRACK(self);
@@ -1805,10 +1826,38 @@ _io_BufferedWriter___init___impl(buffered *self, PyObject *raw,
self->fast_closed_checks = (Py_TYPE(self) == &PyBufferedWriter_Type &&
Py_TYPE(raw) == &PyFileIO_Type);
+ if (self->next == NULL) {
+ self->prev = &buffer_list_end;
+ self->next = buffer_list_end.next;
+ buffer_list_end.next->prev = self;
+ buffer_list_end.next = self;
+ }
+
self->ok = 1;
return 0;
}
+/*
+* Ensure all buffered writers are flushed before proceeding with
+* normal shutdown. Otherwise, if the underlying file objects get
+* finalized before the buffered writer wrapping it then any buffered
+* data will be lost.
+*/
+void _PyIO_atexit_flush(void)
+{
+ while (buffer_list_end.next != &buffer_list_end) {
+ buffered *buf = buffer_list_end.next;
+ remove_from_linked_list(buf);
+ if (buf->ok && !buf->finalizing) {
+ /* good state and not finalizing */
+ Py_INCREF(buf);
+ buffered_flush(buf, NULL);
+ Py_DECREF(buf);
+ PyErr_Clear();
+ }
+ }
+}
+
static Py_ssize_t
_bufferedwriter_raw_write(buffered *self, char *start, Py_ssize_t len)
{