Skip to content

bpo-17852: Maintain a list of BufferedWriter objects. Flush them on exit. #1908

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

Merged
merged 5 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,7 @@ def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE):
self.buffer_size = buffer_size
self._write_buf = bytearray()
self._write_lock = Lock()
_register_writer(self)

def writable(self):
return self.raw.writable()
Expand Down Expand Up @@ -2574,3 +2575,26 @@ def encoding(self):
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Maintain a list of open buffered files, flush them before exiting the
interpreter. Based on a patch from Armin Rigo.
2 changes: 2 additions & 0 deletions Modules/_io/_iomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions Modules/_io/_iomodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,5 @@ extern PyObject *_PyIO_empty_str;
extern PyObject *_PyIO_empty_bytes;

extern PyTypeObject _PyBytesIOBuffer_Type;

extern void _PyIO_atexit_flush(void);
46 changes: 45 additions & 1 deletion Modules/_io/bufferedio.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ bufferediobase_write(PyObject *self, PyObject *args)
}


typedef struct {
typedef struct _buffered {
PyObject_HEAD

PyObject *raw;
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -386,6 +396,15 @@ _enter_buffered_busy(buffered *self)
(self->buffer_size * (size / self->buffer_size)))


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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down