Repository navigation
Conversation
Contributor
|
This pull request has been imported. If you are a Meta employee, you can view this in D123508578. (Because this pull request was imported automatically, there will not be any future comments.) |
## The problem When several DotSlash processes need the same artifact at the same time, all but one of them block on the artifact's download lock. When the first one finishes and releases the lock, the next one acquires it and downloads the artifact again, even though it is already in the cache by then. So does every other waiting process: N concurrent invocations download the artifact N times, one after another, and `mv_no_clobber()` silently discards every copy but the first. I ran into this when `buck2` launched several `rustc` processes at once on a new machine and `rustc` was managed by DotSlash. With a ~180 MB toolchain tarball and five build actions waiting on it, a `buck2` build spent over ten minutes downloading the same tarball again and again. For archive formats, each redundant download also leaves its archive behind in the cache (facebook#168). The problem also happens when different DotSlash files that share an artifact (e.g. `node`, `npm` and `npx`) run concurrently. ## This commit The root of the problem is that callers only call `download_artifact()` after finding the artifact missing from the cache, but `download_artifact()` does not check again once it holds the lock. This adds that check. The check is also correct when no lock could be acquired: the artifact directory is moved into place with a rename, so if the executable exists, the artifact is complete. ## Reproduction/test There's a unit test in the diff, but here's how to reproduce the full problem, with real concurrency, in the shell with `python3` and `curl`. It serves a small file over HTTP with a 2-second delay per request and runs `dotslash -- fetch` on it four times concurrently. ```bash #!/bin/sh # Usage: repro.sh path/to/dotslash set -eu dotslash=$(realpath "$1") cd "$(mktemp -d)" export DOTSLASH_CACHE="$PWD/cache" echo hello > hello.txt platform=$(uname -s | sed 's/Darwin/macos/;s/Linux/linux/')-$(uname -m | sed 's/arm64/aarch64/') cat > hello <<EOF #!/usr/bin/env dotslash {"name": "hello", "platforms": {"$platform": { "size": $(wc -c < hello.txt), "hash": "sha256", "digest": "$("$dotslash" -- sha256 hello.txt)", "path": "hello.txt", "providers": [{"url": "http://127.0.0.1:8765/hello.txt"}] }}} EOF # A file server that takes 2 seconds per request, so that concurrent fetches # queue up on the DotSlash download lock. python3 -c ' import http.server, time class H(http.server.SimpleHTTPRequestHandler): def do_GET(self): time.sleep(2) super().do_GET() http.server.ThreadingHTTPServer(("127.0.0.1", 8765), H).serve_forever() ' 2> server.log & trap "kill $!" EXIT until curl -sf --head http://127.0.0.1:8765/hello.txt > /dev/null; do sleep 0.1 done start=$(date +%s) pids= for i in 1 2 3 4; do "$dotslash" -- fetch ./hello > /dev/null & pids="$pids $!" done wait $pids # Count GET requests, skip HEAD requests by `curl --head` above downloads=$(grep -c 'GET /hello.txt' server.log || true) echo "downloads: $downloads, seconds: $(($(date +%s) - start))" ``` Before this change (macOS, aarch64): ```console $ sh repro.sh ./dotslash-before downloads: 4, seconds: 8 ``` After: ```console $ sh repro.sh target/release/dotslash downloads: 1, seconds: 2 ```
ilyagr
force-pushed
the
double-download-fix
branch
from
October 6, 2026 04:00
7917f87 to
4150140
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
When several DotSlash processes need the same artifact at the same time, all but one of them block on the artifact's download lock. When the first one finishes and releases the lock, the next one acquires it and downloads the artifact again, even though it is already in the cache by then. So does every other waiting process: N concurrent invocations download the artifact N times, one after another, and
mv_no_clobber()silently discards every copy but the first.I ran into this when
buck2launched severalrustcprocesses at once on a new machine andrustcwas managed by DotSlash. With a ~180 MB toolchain tarball and five build actions waiting on it, abuck2build spent over ten minutes downloading the same tarball again and again. For archive formats, each redundant download also leaves its archive behind in the cache (#168).The problem also happens when different DotSlash files that share an artifact (e.g.
node,npmandnpx) run concurrently.This commit
The root of the problem is that callers only call
download_artifact()after finding the artifact missing from the cache, butdownload_artifact()does not check again once it holds the lock. This adds that check.The check is also correct when no lock could be acquired: the artifact directory is moved into place with a rename, so if the executable exists, the artifact is complete.
Reproduction/test
There's a unit test in the diff, but here's how to reproduce the full problem, with real concurrency, in the shell with
python3andcurl. It serves a small file over HTTP with a 2-second delay per request and runsdotslash -- fetchon it four times concurrently.Before this change (macOS, aarch64):
After: