summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/_pyio.py24
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2017-09-04-12-46-25.bpo-17852.OxAtCg.rst2
-rw-r--r--Modules/_io/_iomodule.c2
-rw-r--r--Modules/_io/_iomodule.h2
-rw-r--r--Modules/_io/bufferedio.c46
5 files changed, 75 insertions, 1 deletions
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 4653847..3aa2b24 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -1185,6 +1185,7 @@ class BufferedWriter(_BufferedIOMixin):
self.buffer_size = buffer_size
self._write_buf = bytearray()
self._write_lock = Lock()
+ _register_writer(self)
def writable(self):
return self.raw.writable()
@@ -2574,3 +2575,26 @@ class StringIO(TextIOWrapper):
def detach(self):
# This doesn't make sense on StringIO.
self._unsupported("detach")
+
+
+# ____________________________________________________________
+
+import atexit, weakref
+
+_all_writers = weakref.WeakSet()
+
+def _register_writer(w):
+ # keep weak-ref to buffered writer
+ _all_writers.add(w)
+
+def _flush_all_writers():
+ # 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.
+ for w in _all_writers:
+ try:
+ w.flush()
+ except:
+ pass
+atexit.register(_flush_all_writers)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-09-04-12-46-25.bpo-17852.OxAtCg.rst b/Misc/NEWS.d/next/Core and Builtins/2017-09-04-12-46-25.bpo-17852.OxAtCg.rst
new file mode 100644
index 0000000..185664c
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2017-09-04-12-46-25.bpo-17852.OxAtCg.rst
@@ -0,0 +1,2 @@
+Maintain a list of open buffered files, flush them before exiting the
+interpreter. Based on a patch from Armin Rigo.
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 189b1cd..50c87c1 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -196,7 +196,7 @@ bufferediobase_write(PyObject *self, PyObject *args)
}
-typedef struct {
+typedef struct _buffered {
PyObject_HEAD
PyObject *raw;
@@ -240,8 +240,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:
@@ -387,6 +397,15 @@ _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;
@@ -394,6 +413,8 @@ buffered_dealloc(buffered *self)
return;
_PyObject_GC_UNTRACK(self);
self->ok = 0;
+ if (self->next != NULL)
+ remove_from_linked_list(self);
if (self->weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *)self);
Py_CLEAR(self->raw);
@@ -1817,10 +1838,33 @@ _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);
+ buffered_flush(buf, NULL);
+ PyErr_Clear();
+ }
+}
+
static Py_ssize_t
_bufferedwriter_raw_write(buffered *self, char *start, Py_ssize_t len)
{