Skip to content
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

Close a potentially unclosed body file before it can interfere with writing a new one #343

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

A5rocks
Copy link

@A5rocks A5rocks commented Nov 11, 2024

This is based on debugging in python-trio/trio#3127 and some comments from @graingert, so see that PR for specific information on what was failing (I minimized a failing example) and testing this fix for it (latest commit). Just look at the relevant github actions logs.


This problem only manifests on the combination of PyPy and Windows. Here's how:

  • CacheControlAdapter#send calls conditional_headers
  • conditional_headers calls _load_from_cache
  • _load_from_cache calls body_file = self.cache.get_body(cache_url) and saves it to the response
  • get_body returns an opened file
  • conditional_headers uses the response (which holds the body file) to get headers
  • conditional_headers returns, but doesn't explicitly finish the request
    • on cpython this is fine -- refcounting means the file gets closed
    • on pypy, this keeps the file open for a while longer
  • CacheControlAdapter#send now runs super().send(request, stream, timeout, verify, cert, proxies)
    • this makes the request (good)
    • and saves the body of the new response. note that the body file may still be open on pypy (bad)
      • on unix it is OK that the bodyfile is open and we are trying to os.replace it
      • but on windows it is not OK

Thus an annoying caching bug happens. Hopefully I've described it well enough.


Note that this might only happen with pip's SafeFileCache (which is a subclass of SeparateBodyBaseCache). Some of the details rely on os.replace etc which doesn't happen here. But the fix applies here so...

new_headers = {}
if not resp:
return {}
with resp:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm scratching my head why unraisablehook isn't detecting this missing with. Perhaps requests is closing the connection on GC without issuing a ResourceWarning?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also possible a SeparateBodyCache isn't used in the tests (I haven't checked)?

Copy link
Author

@A5rocks A5rocks Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think test_body_actually_stored_separately doesn't test the case where theres an outdated entry in cache, which is when a ResourceWarning should appear.

Otherwise, conditional_headers either:

  • doesn't have a resp (so no body file to warn)
  • gets shortcircuited around. see
    if cached_response:
    return self.build_response(request, cached_response, from_cache=True)
    # check for etags and add headers if appropriate
    request.headers.update(self.controller.conditional_headers(request))

though I'm not completely sure, cause test_update_cached_response_with_valid_headers_separate_body looks like it tests that case...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants