devpi / devpi

Python PyPi staging server and packaging, testing, release tool
https://doc.devpi.net
887 stars 135 forks source link

refresh requires two clicks/requests #889

Open sanderr opened 2 years ago

sanderr commented 2 years ago

Description

The refresh button / API endpoint consistently requires two clicks/requests to fetch a new package from PyPi. The first does not have any visible effect, the second makes the new package versions appear.

Steps to reproduce

Environment

Arch Linux, Python 3.9.10

Package              Version
-------------------- ---------
aiohttp              3.8.1
aiosignal            1.2.0
argon2-cffi          21.3.0
argon2-cffi-bindings 21.2.0
async-timeout        4.0.2
attrs                21.4.0
certifi              2021.10.8
cffi                 1.15.0
charset-normalizer   2.0.12
defusedxml           0.7.1
devpi-common         3.6.0
devpi-server         6.5.1
frozenlist           1.3.0
hupper               1.10.3
idna                 3.3
itsdangerous         2.1.2
lazy                 1.4
multidict            6.0.2
passlib              1.7.4
PasteDeploy          2.1.1
pip                  22.0.4
plaster              1.0
plaster-pastedeploy  0.7
platformdirs         2.5.2
pluggy               1.0.0
py                   1.11.0
pycparser            2.21
pyramid              2.0
python-dateutil      2.8.2
repoze.lru           0.7
requests             2.27.1
ruamel.yaml          0.17.21
ruamel.yaml.clib     0.2.6
setuptools           62.0.0
six                  1.16.0
strictyaml           1.6.1
translationstring    1.4
urllib3              1.26.9
venusian             3.0.0
waitress             2.1.1
WebOb                1.8.7
wheel                0.37.1
yarl                 1.7.2
zope.deprecation     4.4.0
zope.interface       5.4.0
fschulze commented 2 years ago

I tried to reproduce this and I only got the same behaviour if I didn't wait for the test.pypi.org cache expiry. When I made sure that test.pypi.org simple page actually contained the new release and then clicked refresh, it always worked as expected.

sanderr commented 2 years ago

I'll give this another go soon and take that into account, then report back.

sanderr commented 2 years ago

Did you use Python 3.10? I just tried to reproduce the issue with Python 3.10 and failed, then went back to 3.9 (which I was using when I made the ticket) and succeeded. I'm pretty sure I set up my environments exactly the same otherwise. Could you see if you can reproduce it on Python 3.9?

sanderr commented 2 years ago

The Python version could be a misdiagnose. I'm having trouble getting consistent behavior. It looks like sometimes it works as expected for the first new package version since the server started, but then subsequent packages will consistently result in the issue I described above. Other times even the first does not work. And then there's the inconsistency that you can't reproduce it. Would it help if I'm more exact in the commands I run? Perhaps I can even write a reproductive script. If I can get that to consistently reproduce it on my system I expect it should trigger on yours as well.

fschulze commented 2 years ago

Consistently reproducing it would be very helpful. Keep the caching in mind. It is entirely possible that you can see the change in the browser or with curl, but the next request by devpi-server could hit a feedly server which still has the old version. I would say anything within at least 60s after upload can entirely be attributed to caching.

I tested with Python 3.8.6.

sanderr commented 2 years ago

That's an interesting addendum, I only waited about 10 seconds each time. I've got a script now (I'll share it once it's more or less stable) and I'll add a 120 second wait to check if it might just be that. But from the fact that a second refresh consistently works I honestly suspect it's something else (though I must admit I have little experience here).

sanderr commented 2 years ago

I'm finally getting some consistency with my testing. Alas it looks like I can't reproduce it to the extent I described when I first opened this issue. It's possibly I was too naive in my testing back then, or I might be missing something now. Either way, here's the current situation with a reliable reproduction. My approach: I set up a server with a new server dir, create the index as described above, upload a package to TestPyPi, refresh and check for results. If this didn't work I refresh again and check once more. Results:

Is this expected behavior?

The server where I initially encountered this issue is a long-running one, not one that just got started as in my example scenario. Yet, I consistently got this behavior there as well. That said, I can't say with complete confidence I tried increasing the wait time there, so that could be the explanation.

I've attached my testing script if you want to experiment with this yourself. It's not pretty but it does the job. Usage: usage: TWINE_USERNAME=<testpypi_username> TWINE_PASSWORD=<testpypi_pass> ./reproduce.sh <package_name> <new_version> <nb_attempts>, where <package_name> is the name of a package that does not exist yet or you are the author of on TestPyPi, <new_version> is a version to upload that does not exist yet and <nb_attempts> is the number of loops to run on the same server (I recommend 2 to observe the behavior I described).

script ```sh #!/usr/bin/env sh if [ -z "$1" ] || [ -z "$2" ] || [ -z "$TWINE_USERNAME" ] || [ -z "$TWINE_PASSWORD" ]; then echo "usage: TWINE_USERNAME= TWINE_PASSWORD= ./reproduce.sh " exit 1 fi if [ ! -z "$(pgrep devpi-server)" ]; then echo "A devpi server is already running, kill it first" exit 1 fi package_name="$1" new_version="$2" nb_attempts="$3" working_dir=$(mktemp -d) env_dir="${working_dir}/env" bin="${env_dir}/bin" server_dir="${working_dir}/server" devpi_address=127.0.0.1:3141 # install devpi python3.8 -m venv "${env_dir}" "${bin}/pip" install -U pip "${bin}/pip" install bumpversion twine devpi-server devpi-client # start server "${bin}/devpi-init" --serverdir "${server_dir}" "${bin}/devpi-server" --serverdir "${server_dir}" > /dev/null & server_process=$! # wait until server is up echo -n "Waiting for server to come up" until [ 200 -eq "$(curl -s -o /dev/null -w %{http_code} "http://${devpi_address}")" ]; do echo -n "." sleep 0.2 done echo "" # configure devpi client and server "${bin}/devpi" use "http://${devpi_address}" "${bin}/devpi" login root --password '' "${bin}/devpi" index --create root/testpypi type=mirror volatile=False mirror_url=https://test.pypi.org/simple/ mirror_web_url_fmt=https://test.pypi.org/project/{name}/ title=TestPyPi # prepare package package_dir="${working_dir}/${package_name}" mkdir "${package_dir}" cat << EOF > "${package_dir}/pyproject.toml" [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" EOF cat << EOF > "${package_dir}/setup.cfg" [metadata] name = ${package_name} version = ${new_version} [options] packages = EOF cat << EOF > "${package_dir}/.bumpversion.cfg" [bumpversion] current_version = ${new_version} [bumpversion:file:setup.cfg] EOF for attempt in $(seq "${nb_attempts}"); do # build package rm -rf "${package_dir}/dist" "${bin}/python" -m build --wheel "${package_dir}" > /dev/null # upload package "${bin}/twine" upload --repository testpypi "${package_dir}/dist/*" if [ ! 0 -eq "$?" ]; then echo "Failed to upload package, perhaps the supplied version already exists?" echo "Exiting" kill "${server_process}" exit 1 fi # wait until package is available on TestPyPi version=$(basename $(ls "${package_dir}/dist/") | sed 's/[^0-9]*\([0-9.]*\).*/\1/') echo -n "Waiting for package to become available on TestPyPi" until [ "null" != "$(curl --silent --show-error "https://test.pypi.org/pypi/${package_name}/json" | jq ".releases[\"${version}\"][0]")" ]; do echo -n "." sleep 0.2 done echo "" for refresh_count in $(seq 2); do # only wait for first refresh if [ "${refresh_count}" -eq 1 ]; then echo "Waiting two more minutes to better rule out a timing/caching issue" sleep 120 fi # refresh package echo "Refreshing package" curl -X POST --silent "http://${devpi_address}/root/testpypi/+simple/${package_name}/refresh" > /dev/null # only wait for first refresh if [ "${refresh_count}" -eq 1 ]; then echo "Waiting two more minutes to better rule out a timing/caching issue" sleep 120 fi echo "Checking if package can be found" # check if package can be found if [ -z "$(curl --silent --show-error "http://${devpi_address}/root/testpypi/+simple/${package_name}/" | grep --fixed-strings "${version}")" ]; then echo "ATTEMPT ${attempt} REFRESH ${refresh_count}: ${version} NOT FOUND" else echo "ATTEMPT ${attempt} REFRESH ${refresh_count}: ${version} FOUND" # refresh was a success => no need to try again break fi done # bump version for next attempt (cd "${package_dir}" && "${bin}/bumpversion" patch) done echo "Killing server process" kill "${server_process}" ```