mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Issue #10478: Reentrant calls inside buffered IO objects (for example by
way of a signal handler) now raise a RuntimeError instead of freezing the current process.
This commit is contained in:
parent
38e117d20a
commit
f3b68b3f98
3 changed files with 94 additions and 19 deletions
|
@ -225,6 +225,7 @@ typedef struct {
|
|||
|
||||
#ifdef WITH_THREAD
|
||||
PyThread_type_lock lock;
|
||||
volatile long owner;
|
||||
#endif
|
||||
|
||||
Py_ssize_t buffer_size;
|
||||
|
@ -260,17 +261,34 @@ typedef struct {
|
|||
/* These macros protect the buffered object against concurrent operations. */
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
#define ENTER_BUFFERED(self) \
|
||||
if (!PyThread_acquire_lock(self->lock, 0)) { \
|
||||
Py_BEGIN_ALLOW_THREADS \
|
||||
PyThread_acquire_lock(self->lock, 1); \
|
||||
Py_END_ALLOW_THREADS \
|
||||
|
||||
static int
|
||||
_enter_buffered_busy(buffered *self)
|
||||
{
|
||||
if (self->owner == PyThread_get_thread_ident()) {
|
||||
PyErr_Format(PyExc_RuntimeError,
|
||||
"reentrant call inside %R", self);
|
||||
return 0;
|
||||
}
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyThread_acquire_lock(self->lock, 1);
|
||||
Py_END_ALLOW_THREADS
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define ENTER_BUFFERED(self) \
|
||||
( (PyThread_acquire_lock(self->lock, 0) ? \
|
||||
1 : _enter_buffered_busy(self)) \
|
||||
&& (self->owner = PyThread_get_thread_ident(), 1) )
|
||||
|
||||
#define LEAVE_BUFFERED(self) \
|
||||
PyThread_release_lock(self->lock);
|
||||
do { \
|
||||
self->owner = 0; \
|
||||
PyThread_release_lock(self->lock); \
|
||||
} while(0);
|
||||
|
||||
#else
|
||||
#define ENTER_BUFFERED(self)
|
||||
#define ENTER_BUFFERED(self) 1
|
||||
#define LEAVE_BUFFERED(self)
|
||||
#endif
|
||||
|
||||
|
@ -444,7 +462,8 @@ buffered_close(buffered *self, PyObject *args)
|
|||
int r;
|
||||
|
||||
CHECK_INITIALIZED(self)
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
|
||||
r = buffered_closed(self);
|
||||
if (r < 0)
|
||||
|
@ -465,7 +484,8 @@ buffered_close(buffered *self, PyObject *args)
|
|||
/* flush() will most probably re-take the lock, so drop it first */
|
||||
LEAVE_BUFFERED(self)
|
||||
res = PyObject_CallMethodObjArgs((PyObject *)self, _PyIO_str_flush, NULL);
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
if (res == NULL) {
|
||||
goto end;
|
||||
}
|
||||
|
@ -679,6 +699,7 @@ _buffered_init(buffered *self)
|
|||
PyErr_SetString(PyExc_RuntimeError, "can't allocate read lock");
|
||||
return -1;
|
||||
}
|
||||
self->owner = 0;
|
||||
#endif
|
||||
/* Find out whether buffer_size is a power of 2 */
|
||||
/* XXX is this optimization useful? */
|
||||
|
@ -705,7 +726,8 @@ buffered_flush(buffered *self, PyObject *args)
|
|||
CHECK_INITIALIZED(self)
|
||||
CHECK_CLOSED(self, "flush of closed file")
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
res = _bufferedwriter_flush_unlocked(self, 0);
|
||||
if (res != NULL && self->readable) {
|
||||
/* Rewind the raw stream so that its position corresponds to
|
||||
|
@ -732,7 +754,8 @@ buffered_peek(buffered *self, PyObject *args)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
|
||||
if (self->writable) {
|
||||
res = _bufferedwriter_flush_unlocked(self, 1);
|
||||
|
@ -767,7 +790,8 @@ buffered_read(buffered *self, PyObject *args)
|
|||
|
||||
if (n == -1) {
|
||||
/* The number of bytes is unspecified, read until the end of stream */
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
res = _bufferedreader_read_all(self);
|
||||
LEAVE_BUFFERED(self)
|
||||
}
|
||||
|
@ -775,7 +799,8 @@ buffered_read(buffered *self, PyObject *args)
|
|||
res = _bufferedreader_read_fast(self, n);
|
||||
if (res == Py_None) {
|
||||
Py_DECREF(res);
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
res = _bufferedreader_read_generic(self, n);
|
||||
LEAVE_BUFFERED(self)
|
||||
}
|
||||
|
@ -803,7 +828,8 @@ buffered_read1(buffered *self, PyObject *args)
|
|||
if (n == 0)
|
||||
return PyBytes_FromStringAndSize(NULL, 0);
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
|
||||
if (self->writable) {
|
||||
res = _bufferedwriter_flush_unlocked(self, 1);
|
||||
|
@ -859,7 +885,8 @@ buffered_readinto(buffered *self, PyObject *args)
|
|||
|
||||
/* TODO: use raw.readinto() instead! */
|
||||
if (self->writable) {
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
res = _bufferedwriter_flush_unlocked(self, 0);
|
||||
LEAVE_BUFFERED(self)
|
||||
if (res == NULL)
|
||||
|
@ -903,7 +930,8 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
|
|||
goto end_unlocked;
|
||||
}
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
goto end_unlocked;
|
||||
|
||||
/* Now we try to get some more from the raw stream */
|
||||
if (self->writable) {
|
||||
|
@ -1053,7 +1081,8 @@ buffered_seek(buffered *self, PyObject *args)
|
|||
}
|
||||
}
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
|
||||
/* Fallback: invoke raw seek() method and clear buffer */
|
||||
if (self->writable) {
|
||||
|
@ -1091,7 +1120,8 @@ buffered_truncate(buffered *self, PyObject *args)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self))
|
||||
return NULL;
|
||||
|
||||
if (self->writable) {
|
||||
res = _bufferedwriter_flush_unlocked(self, 0);
|
||||
|
@ -1748,7 +1778,10 @@ bufferedwriter_write(buffered *self, PyObject *args)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ENTER_BUFFERED(self)
|
||||
if (!ENTER_BUFFERED(self)) {
|
||||
PyBuffer_Release(&buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Fast path: the data to write can be fully buffered. */
|
||||
if (!VALID_READ_BUFFER(self) && !VALID_WRITE_BUFFER(self)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue