From: Nir Soffer Date: Thu, 7 Dec 2017 20:25:39 +0000 (+0200) Subject: [2.7] bpo-32186: Release the GIL during fstat and lseek calls (#4651) X-Git-Tag: v2.7.15rc1~103 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=830daae1c82ed33deef0086b7b6323e5be0b0cc8;p=thirdparty%2FPython%2Fcpython.git [2.7] bpo-32186: Release the GIL during fstat and lseek calls (#4651) In fileio, there were 3 fstat() calls and one lseek() call that did not release the GIL during the call. This can cause all threads to hang for unlimited time when using io.FileIO with inaccessible NFS server. Same issue seen in fileio exists also in fileobject, fixed in the same way. --- diff --git a/Misc/NEWS.d/next/Library/2017-11-30-20-33-22.bpo-32186.O42bVe.rst b/Misc/NEWS.d/next/Library/2017-11-30-20-33-22.bpo-32186.O42bVe.rst new file mode 100644 index 000000000000..66c4468e80c0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-11-30-20-33-22.bpo-32186.O42bVe.rst @@ -0,0 +1,4 @@ +Creating io.FileIO() and builtin file() objects now release the GIL when +checking the file descriptor. io.FileIO.readall(), io.FileIO.read(), and +file.read() now release the GIL when getting the file size. Fixed hang of all +threads with inaccessible NFS server. Patch by Nir Soffer. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 4a71a57ec0de..2b40ada195a1 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -146,9 +146,15 @@ dircheck(fileio* self, PyObject *nameobj) { #if defined(HAVE_FSTAT) && defined(S_IFDIR) && defined(EISDIR) struct stat buf; + int res; if (self->fd < 0) return 0; - if (fstat(self->fd, &buf) == 0 && S_ISDIR(buf.st_mode)) { + + Py_BEGIN_ALLOW_THREADS + res = fstat(self->fd, &buf); + Py_END_ALLOW_THREADS + + if (res == 0 && S_ISDIR(buf.st_mode)) { errno = EISDIR; PyErr_SetFromErrnoWithFilenameObject(PyExc_IOError, nameobj); return -1; @@ -162,17 +168,34 @@ check_fd(int fd) { #if defined(HAVE_FSTAT) struct stat buf; - if (!_PyVerify_fd(fd) || (fstat(fd, &buf) < 0 && errno == EBADF)) { - PyObject *exc; - char *msg = strerror(EBADF); - exc = PyObject_CallFunction(PyExc_OSError, "(is)", - EBADF, msg); - PyErr_SetObject(PyExc_OSError, exc); - Py_XDECREF(exc); - return -1; + int res; + PyObject *exc; + char *msg; + + if (!_PyVerify_fd(fd)) { + goto badfd; } -#endif + + Py_BEGIN_ALLOW_THREADS + res = fstat(fd, &buf); + Py_END_ALLOW_THREADS + + if (res < 0 && errno == EBADF) { + goto badfd; + } + return 0; + +badfd: + msg = strerror(EBADF); + exc = PyObject_CallFunction(PyExc_OSError, "(is)", + EBADF, msg); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); + return -1; +#else + return 0; +#endif } @@ -519,9 +542,19 @@ new_buffersize(fileio *self, size_t currentsize) #ifdef HAVE_FSTAT off_t pos, end; struct stat st; - if (fstat(self->fd, &st) == 0) { + int res; + + Py_BEGIN_ALLOW_THREADS + res = fstat(self->fd, &st); + Py_END_ALLOW_THREADS + + if (res == 0) { end = st.st_size; + + Py_BEGIN_ALLOW_THREADS pos = lseek(self->fd, 0L, SEEK_CUR); + Py_END_ALLOW_THREADS + /* Files claiming a size smaller than SMALLCHUNK may actually be streaming pseudo-files. In this case, we apply the more aggressive algorithm below. diff --git a/Objects/fileobject.c b/Objects/fileobject.c index 2f63c374d1e2..8d1c5812f0d2 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -121,10 +121,15 @@ dircheck(PyFileObject* f) { #if defined(HAVE_FSTAT) && defined(S_IFDIR) && defined(EISDIR) struct stat buf; + int res; if (f->f_fp == NULL) return f; - if (fstat(fileno(f->f_fp), &buf) == 0 && - S_ISDIR(buf.st_mode)) { + + Py_BEGIN_ALLOW_THREADS + res = fstat(fileno(f->f_fp), &buf); + Py_END_ALLOW_THREADS + + if (res == 0 && S_ISDIR(buf.st_mode)) { char *msg = strerror(EISDIR); PyObject *exc = PyObject_CallFunction(PyExc_IOError, "(isO)", EISDIR, msg, f->f_name); @@ -1010,7 +1015,13 @@ new_buffersize(PyFileObject *f, size_t currentsize) #ifdef HAVE_FSTAT off_t pos, end; struct stat st; - if (fstat(fileno(f->f_fp), &st) == 0) { + int res; + + Py_BEGIN_ALLOW_THREADS + res = fstat(fileno(f->f_fp), &st); + Py_END_ALLOW_THREADS + + if (res == 0) { end = st.st_size; /* The following is not a bug: we really need to call lseek() *and* ftell(). The reason is that some stdio libraries @@ -1021,7 +1032,11 @@ new_buffersize(PyFileObject *f, size_t currentsize) works. We can't use the lseek() value either, because we need to take the amount of buffered data into account. (Yet another reason why stdio stinks. :-) */ + + Py_BEGIN_ALLOW_THREADS pos = lseek(fileno(f->f_fp), 0L, SEEK_CUR); + Py_END_ALLOW_THREADS + if (pos >= 0) { pos = ftell(f->f_fp); }