-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
gh-89727: Fix os.fwalk RecursionError on deep trees #100347
Conversation
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
TODO:
|
Use a stack to implement os.walk iteratively instead of recursively to avoid hitting recursion limits on deeply nested trees.
…2015) Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Christian Heimes <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]> Fixes python#89051
…ions in imaplib docs (python#99625)
…with `_read_ready_cb` (python#100349)
Make sure file descriptors get closed if an exception occurs right after the file descriptor was popped from the stack. Also catch exceptions thrown by calling os.close on an already-closed file-descriptor when doing file descriptor cleanup.
Note that handling of a single exception should function the same, but repeated errors will interrupt the file descriptor cleanup (closing). Imagine a user holds down try:
...
except:
for fd in reversed(fd_stack):
try:
close(fd)
except OSError:
pass
raise I'm not sure this is a problem. If I understand correctly, neither versions handles this perfectly. It seemed worth mentioning though. I thought about changing the |
Opening this up for review in response to #89727 (comment) |
If an exception occurred between `close(fd_stack[-1])` and `fd_stack.pop()`, then we would close the fd a second time in the except clause. It would be better to risk leaving the fd open, as the previous implementation of fwalk could
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.
LGTM, nice work!
A thought: could you merge |
Lib/os.py
Outdated
except: | ||
for action, value in reversed(stack): | ||
if action is _WalkAction.CLOSE: | ||
try: | ||
close(value) | ||
except OSError: | ||
pass | ||
raise |
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.
Could this be a finally:
block?
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.
Sure, would you consider that more readable or explicit? I just didn't see any benefit, since this is only needed if an exception occurs. The only functional difference would be that we'd iterate over an empty list if no exceptions occur right?
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 think I'd consider it more explicit, though feel free to wait for another opinion if you'd like.
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'd also prefer finally
here. Not sure if it actually delivers any functional benefit here (can't remember the exact semantics off the top of my head...), but it does feel at least more readable to me.
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.
Makes sense, thanks for clarifying. I'll change it to finally
. Functionally, I think finally
is slightly worse but it's negligible.
In this case, changing to finally
is functionally equivalent to adding an else
clause with the same for-loop:
try:
...
except:
for action, value in reversed(stack): ...
raise
else:
for action, value in reversed(stack): ...
If no exception occurs in the try
, then stack
will be empty and all of the fds will have been closed already. So changing to finally
means we're adding a step where we simply iterate over an empty list - unnecessary, but costs almost nothing.
To me, a bare except
indicates a cleanup step, but finally
is even more explicit, so it probably makes sense here.
@jonburdo I've fixed this in another PR - #119638. Shamefully I forgot about this PR :(. Comparing our code, I think we arrived at similar solutions, the main difference being that I moved the |
@barneygale No worries! Thanks for mentioning it. I meant to follow up on this. I'll take a look at your PR. Thanks for working on this function and the related ones! It feels great to see these improvements :) |
Use a stack to implement os.fwalk iteratively instead of recursively to avoid hitting recursion limits on deeply nested trees.
Similar to how this is done for
os.walk
in #99803