diff options
-rw-r--r-- | Lib/_pyio.py | 24 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2017-09-04-12-46-25.bpo-17852.OxAtCg.rst | 2 | ||||
-rw-r--r-- | Modules/_io/_iomodule.c | 2 | ||||
-rw-r--r-- | Modules/_io/_iomodule.h | 2 | ||||
-rw-r--r-- | Modules/_io/bufferedio.c | 46 |
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) { |