Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Merged
merged 11 commits into from
Nov 1, 2024

Conversation

cmaloney
Copy link
Contributor

@cmaloney cmaloney commented Oct 8, 2024

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

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.
if (internal_close(self) < 0)
/* Have to close the existing file first.

This leaves the stat so we can reuse the memory. */
Copy link
Member

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().

@@ -455,11 +458,17 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
#endif
}

PyMem_Free(self->stat_atopen);
Copy link
Contributor Author

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...).

Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

@vstinner vstinner left a 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.

@cmaloney
Copy link
Contributor Author

cmaloney commented Oct 9, 2024

I think the latest commit fixes the compiler warnings (was a testing/debugging assert I accidentally committed with a missing ;, latest changes remove that assert). Not able to find them in the latest changes.

@cmaloney
Copy link
Contributor Author

CI "Tests / Ubuntu SSL tests with OpenSSL" failed because of what looks like a network hang / not these changes: Received 164773440 of 168967744 (97.5%), 0.0 MBs/sec while doing "Configure ccache action"

@@ -178,6 +180,8 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls)
PyErr_Clear();
}
}
PyMem_Free(self->stat_atopen);
self->stat_atopen = NULL;
Copy link
Member

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?

Copy link
Contributor Author

@cmaloney cmaloney Oct 31, 2024

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.

@@ -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;
Copy link
Member

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?

Copy link
Contributor Author

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

@@ -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) {
Copy link
Member

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.

@vstinner vstinner merged commit 72dd471 into python:main Nov 1, 2024
39 checks passed
@vstinner
Copy link
Member

vstinner commented Nov 1, 2024

Merged, thank you.

@cmaloney cmaloney deleted the cmaloney/fd_cleanup branch November 1, 2024 21:55
picnixz pushed a commit to picnixz/cpython that referenced this pull request Dec 8, 2024
…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.
ebonnal pushed a commit to ebonnal/cpython that referenced this pull request Jan 12, 2025
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants