-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-120754: Ensure _stat_atopen is cleared on fd change #125166
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
Conversation
Performed an audit of `fileio.c` and `_pyio` and made sure anytime the fd changes the stat result, if set, is also cleared/changed. There's one case where it's not cleared, if code would clear it in __init__, keep the memory allocated and just do another fstat with the existing memory.
Modules/_io/fileio.c
Outdated
if (internal_close(self) < 0) | ||
/* Have to close the existing file first. | ||
|
||
This leaves the stat so we can reuse the memory. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that this optimization is worth it, I would prefer to always clear the cache in close().
Modules/_io/fileio.c
Outdated
@@ -455,11 +458,17 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, | |||
#endif | |||
} | |||
|
|||
PyMem_Free(self->stat_atopen); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure which is preferred here. The Free + New back to back feels weird, but version with the conditional feels like "extra work/code that should never be executed" (but that the compiler / tools that run currently won't validate...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe if (ptr != NULL) free(ptr)
would be more regular and avoid the 4 lines long comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to that, dropped comment and simplified / back to always calling PyMem_New
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are compiler warnings that you can see in the review tab.
I think the latest commit fixes the compiler warnings (was a testing/debugging assert I accidentally committed with a missing |
CI "Tests / Ubuntu SSL tests with OpenSSL" failed because of what looks like a network hang / not these changes: |
Modules/_io/fileio.c
Outdated
@@ -178,6 +180,8 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls) | |||
PyErr_Clear(); | |||
} | |||
} | |||
PyMem_Free(self->stat_atopen); | |||
self->stat_atopen = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal_close() already does the same, so it's redundant, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tradeoff for matching what _pyio
does which does os.close()
in these cases vs an internal_close
that does the OS close + other cleanup. Happy to drop though / definitely feels redundant to me in these two cases.
Modules/_io/fileio.c
Outdated
@@ -266,10 +270,13 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, | |||
assert(PyFileIO_Check(state, self)); | |||
#endif | |||
if (self->fd >= 0) { | |||
PyMem_Free(self->stat_atopen); | |||
self->stat_atopen = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal_close() already does the same, so it's redundant, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moderately, in the self->closefd
is false case behavior would differ from _pyio, but that will be caught by the PyMem_Free + PyMem_New below, so think this is always redundant / not needed
Modules/_io/fileio.c
Outdated
@@ -455,7 +462,9 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, | |||
#endif | |||
} | |||
|
|||
PyMem_Free(self->stat_atopen); | |||
if (self->stat_atopen != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyMem_Free(NULL) is well defined: it does nothing, so you might omit the if (stat_atopen != NULL)
test.
Merged, thank you. |
…n#125166) Performed an audit of `fileio.c` and `_pyio` and made sure anytime the fd changes the stat result, if set, is also cleared/changed. There's one case where it's not cleared, if code would clear it in __init__, keep the memory allocated and just do another fstat with the existing memory.
…n#125166) Performed an audit of `fileio.c` and `_pyio` and made sure anytime the fd changes the stat result, if set, is also cleared/changed. There's one case where it's not cleared, if code would clear it in __init__, keep the memory allocated and just do another fstat with the existing memory.
While adding _stat_atopen I accidentally created a leak in the C implementation because the
_stat_atopen
member was set to NULL in the C implementation, but the memory wasn't freed. That was resolved, but it showed there were a number of potential cases where_stat_atopen
should be cleared (the information is out of date because the fd changed) that it was not. In this PR I audited by reading through FileIO (in _io and _pyio), and in all cases where the fd is changed/closed made sure to clear/reset _stat_atopen as well.Followup from: #124225 (comment), cc: @vstinner