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

pypi, retry when receiving a 200 with empty body #4717

Conversation

j-martin
Copy link

@j-martin j-martin commented Nov 5, 2021

For some reason, pypi is returning a 200 and an empty body from time to time. The next time you query the same endpoint it returns a proper JSON object. This is not fixing a bug in poetry, but working around the issue.

This was resulting in issue where anything operation modifying the lock file would randomly fail due to JSONDecodeError. This is unrelated to #4592

Pull Request Check List

Resolves: Issue not created.

  • Added tests for changed code. <=== N/A
  • Updated documentation for changed code. <=== N/A

For some reason, pypi is returning a 200 and an empty body from
time to time. The next time you query the same endpoint it
returns a proper JSON object. This is not fixing a bug in
poetry, but working around the issue.
@j-martin j-martin force-pushed the jm/pypi-retry-when-receiving-a-200-with-empty-body branch from 8ea86ad to 2939ca5 Compare November 5, 2021 23:16
@j-martin j-martin changed the base branch from master to 1.1 November 5, 2021 23:16
@kapilt
Copy link

kapilt commented Nov 7, 2021

i'm also seeing this, its gotten to the point that poetry almost feels unusable if not unreliable. looking at the headers there's nothing in particular distinguishing the response.

i dropped a pdb in to introspect the headers

   4  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/pypi_repository.py:326 in _get                                                                              
       324│                                                                                    
       325│         import pdb; pdb.set_trace()                                                
     → 326│         json_data = json_response.json()                          
       327│                                                                                    
       328│         return json_data          
(Pdb) json_response.content
b''
(Pdb) pp dict(json_response.headers)
{'Accept-Ranges': 'bytes',
 'Access-Control-Allow-Headers': 'Content-Type, If-Match, If-Modified-Since, '
                                 'If-None-Match, If-Unmodified-Since',
 'Access-Control-Allow-Methods': 'GET',
 'Access-Control-Allow-Origin': '*',
 'Access-Control-Expose-Headers': 'X-PyPI-Last-Serial',
 'Access-Control-Max-Age': '86400',
 'Cache-Control': 'max-age=900, public',
 'Connection': 'keep-alive',
 'Content-Encoding': 'gzip',
 'Content-Length': '9341',
 'Content-Security-Policy': "base-uri 'self'; block-all-mixed-content; "
                            "connect-src 'self' https://api.github.com/repos/ "
                            '*.fastly-insights.com sentry.io '
                            'https://api.pwnedpasswords.com '
                            'https://2p66nmmycsj3.statuspage.io; default-src '
                            "'none'; font-src 'self' fonts.gstatic.com; "
                            "form-action 'self'; frame-ancestors 'none'; "
                            "frame-src 'none'; img-src 'self' "
                            'https://warehouse-camo.ingress.cmh1.psfhosted.org/ '
                            'www.google-analytics.com *.fastly-insights.com; '
                            "script-src 'self' www.googletagmanager.com "
                            'www.google-analytics.com *.fastly-insights.com '
                            "https://cdn.ravenjs.com; style-src 'self' "
                            'fonts.googleapis.com; worker-src '
                            '*.fastly-insights.com',
 'Content-Type': 'application/json',
 'Date': 'Sun, 07 Nov 2021 15:11:35 GMT',
 'ETag': '"JuWbHOCwMq+jjOgbucA39g"',
 'Referrer-Policy': 'origin-when-cross-origin',
 'Server': 'nginx/1.13.9',
 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
 'Vary': 'Accept-Encoding',
 'X-Cache': 'HIT',
 'X-Cache-Hits': '1',
 'X-Content-Type-Options': 'nosniff',
 'X-Frame-Options': 'deny',
 'X-Permitted-Cross-Domain-Policies': 'none',
 'X-PyPI-Last-Serial': '8237314',
 'X-Served-By': 'cache-wdc5550-WDC',
 'X-Timer': 'S1636297896.913867,VS0,VE1',
 'X-XSS-Protection': '1; mode=block'}
(Pdb) json_response.__dict__.keys()
dict_keys(['_content', '_content_consumed', '_next', 'status_code', 'headers', 'raw', 'url', 'encoding', 'history', 'reason', 'cookies', 'elapsed', 'request', 'connection', 'from_cache'])
(Pdb) json_response.request
<PreparedRequest [GET]>
(Pdb) json_response.request.__dict__
{'method': 'GET', 'url': 'https://pypi.org/pypi/cached-property/json', 'headers': {'User-Agent': 'python-requests/2.26.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'If-None-Match': '"JuWbHOCwMq+jjOgbucA39g"'}, '_cookies': <RequestsCookieJar[]>, 'body': None, 'hooks': {'response': []}, '_body_position': None}

using the patch in this pr does make things work... its also illustrative of how common the problem is given the number of empty body retries, and how problematic poetry is without it. as else each of those would have have been a jsondecode error.

❯ poetry show -vvv -o
Using virtualenv: /home/kapilt/.pyenv/versions/3.9.1/envs/c7n-3.9
Got empty response from PyPI for pypi/cffi/json. Retrying...
Got empty response from PyPI for pypi/charset-normalizer/json. Retrying...
Got empty response from PyPI for pypi/click/json. Retrying...
Got empty response from PyPI for pypi/docutils/json. Retrying...
Got empty response from PyPI for pypi/idna/json. Retrying...
Got empty response from PyPI for pypi/importlib-metadata/json. Retrying...
Got empty response from PyPI for pypi/iniconfig/json. Retrying...
Got empty response from PyPI for pypi/jmespath/json. Retrying...
Got empty response from PyPI for pypi/jsonschema/json. Retrying...
Got empty response from PyPI for pypi/mock/json. Retrying...
Got empty response from PyPI for pypi/packaging/json. Retrying...
Got empty response from PyPI for pypi/pluggy/json. Retrying...
Got empty response from PyPI for pypi/py/json. Retrying...
Got empty response from PyPI for pypi/pycparser/json. Retrying...
Got empty response from PyPI for pypi/pygit2/json. Retrying...
Got empty response from PyPI for pypi/pyparsing/json. Retrying...
Got empty response from PyPI for pypi/pyrsistent/json. Retrying...
Got empty response from PyPI for pypi/pytest/json. Retrying...
Got empty response from PyPI for pypi/python-dateutil/json. Retrying...
Got empty response from PyPI for pypi/pyyaml/json. Retrying...
Got empty response from PyPI for pypi/requests/json. Retrying...
Got empty response from PyPI for pypi/s3transfer/json. Retrying...
Got empty response from PyPI for pypi/six/json. Retrying...
Got empty response from PyPI for pypi/tabulate/json. Retrying...
Got empty response from PyPI for pypi/toml/json. Retrying...
Got empty response from PyPI for pypi/urllib3/json. Retrying...
Got empty response from PyPI for pypi/zipp/json. Retrying...
docutils   0.17.1 0.18  Docutils -- Python Documentation Utilities
jsonschema 3.2.0  4.2.1 An implementation of JSON Schema validation for Python
pygit2     1.5.0  1.7.0 Python bindings for libgit2.
pyparsing  2.4.7  3.0.4 Python parsing module
pyyaml     5.4.1  6.0   YAML parser and emitter for Python

@austinbutler
Copy link

Has anyone reported this upstream as well? I assume https://github.com/pypa/warehouse/issues would be the right place?

@austinbutler
Copy link

How certain are you that PyPi is the problem? I haven't been able to replicate with curl or requests. I do see Poetry has its own cache of sorts so I wonder if it's something in Poetry after all.

@kapilt
Copy link

kapilt commented Nov 9, 2021

@austinbutler haven't reported upstream (nor do i see another report against recent issues), re curl/requests, it could be a particular set of headers.

@neersighted neersighted added status/needs-consensus Consensus among maintainers required status/external-issue Issue is caused by external project (platform, dep, etc) labels Nov 11, 2021
@neersighted
Copy link
Member

I'm not sure/convinced this is the right approach -- like has been mentioned upthread, attempting to determine if this is to do with a bug in a Poetry dependency, or in PyPI itself, is likely more productive.

@j-martin do you have any particular steps that can reproduce the empty response issue?

@j-martin
Copy link
Author

@neersighted it seems to be fairly random and is definitely an issue on pypi's side. I have not encountered it lately, but one of my coworkers just hit the same issue.

I have opened pypi/warehouse#10387 as suggested by @austinbutler.

@kapilt
Copy link

kapilt commented Nov 22, 2021

per the suggestion on pypi warehouse issue, i tried disabling the cache control and can confirm that it works without any retries.

afaics cache control usage here is independent of the package cache ('artifacts' vs cache/repositories), so can safely be disabled without significant ux behavior effects.

Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 29, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
status/external-issue Issue is caused by external project (platform, dep, etc) status/needs-consensus Consensus among maintainers required
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants