You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Send artifacts to the client while a cache miss is stored #596
On a cache miss the proxy stores the whole artifact before it sends the first byte. Clients with an idle timeout give up while a large artifact downloads:
git-lfs after 30 seconds without data (lfs.activitytimeout), which matters for LFS objects with Cache Git LFS objects #595;
Since 2ed0327 the fetch keeps running after the client leaves, and a retry joins it. So the artifact does get cached, but the first clone or install fails or has to go through several retries. It works only if the download fits within the client's retries. A download longer than artifact_timeout never gets cached, and the whole response, download included, also has to fit within write_timeout.
Proposal: on a miss, send the bytes to the client while they are written to storage, with the hash computed along the way. StreamArtifacts already sends without storing.
If the hash or declared integrity doesn't match at the end, the stored copy is discarded and the client's chunked response is left unterminated, so the client rejects it. This is how StreamArtifacts already handles a mismatch.
Requests that join the same fetch keep waiting for the stored artifact as now. Letting them read the growing file could follow.
Scanners in block mode need the whole file before anything is served, so with those configured a miss stays as it is now.
If the client disconnects, the download keeps going, as it does today for detached fetches.
Open question: should this be the default for every ecosystem, or opt-in or per ecosystem? Package archives are mostly small, and the benefit is mainly for LFS objects, container layers and large packages.
A related case: if Storage.Store fails, for example because the disk is full, storeArtifact returns "storing artifact: …" and the client gets a 502, although the upstream answered. With the bytes already going to the client, a storage error could just drop the stored copy and let the response finish. rudolfs handles a failed cache write the same way.
Yes, let's do this. Make it the default for every ecosystem, except when a block-mode scanner is configured, where a miss stays as it is now. Small archives arrive just as fast either way; the only visible change for them is a chunked response without Content-Length, which cache_artifacts: false already sends. A per-ecosystem switch would add config without a case that needs it.
Include the storage failure case from your comment: once bytes are going to the client, a failed Storage.Store drops the stored copy and lets the response finish. Log it at error level and count it in a metric, so a full disk doesn't go unnoticed while every request still succeeds.
Please test it through a handler with a slow upstream, checking the client gets its first bytes before the upstream has finished, as well as the mismatch and storage failure paths.
Activity
simonchrz commentedon Oct 25, 2026
A related case: if
Storage.Storefails, for example because the disk is full,storeArtifactreturns "storing artifact: …" and the client gets a 502, although the upstream answered. With the bytes already going to the client, a storage error could just drop the stored copy and let the response finish. rudolfs handles a failed cache write the same way.andrew commentedon Oct 25, 2026
Yes, let's do this. Make it the default for every ecosystem, except when a
block-mode scanner is configured, where a miss stays as it is now. Small archives arrive just as fast either way; the only visible change for them is a chunked response withoutContent-Length, whichcache_artifacts: falsealready sends. A per-ecosystem switch would add config without a case that needs it.Include the storage failure case from your comment: once bytes are going to the client, a failed
Storage.Storedrops the stored copy and lets the response finish. Log it at error level and count it in a metric, so a full disk doesn't go unnoticed while every request still succeeds.Please test it through a handler with a slow upstream, checking the client gets its first bytes before the upstream has finished, as well as the mismatch and storage failure paths.