@@ -292,6 +292,40 @@ v2Router.get("/:name+/blobs/:digest", async (req, env: Env, context: ExecutionCo
292292
293293 layerResponse = response ;
294294 const [ s1 , s2 ] = layerResponse . stream . tee ( ) ;
295+
296+ // Parallel tee consumption optimization: enabled by default when PUSH_COMPATIBILITY_MODE !== "none"
297+ // This significantly reduces backpressure recovery time (from 10-30 seconds to 1-5 seconds)
298+ const useParallelConsumption = env . PUSH_COMPATIBILITY_MODE !== "none" ;
299+
300+ if ( useParallelConsumption ) {
301+ // Parallel mode: immediately start R2 upload while returning response to client
302+ // This allows R2 write to continue even when client pauses reading
303+ const layerSize = layerResponse . size ;
304+ const uploadPromise = env . REGISTRY_CLIENT . monolithicUpload ( name , digest , s2 , layerSize ) ;
305+
306+ // Use waitUntil to ensure upload doesn't block response, but upload has already started in parallel
307+ context . waitUntil (
308+ wrap ( uploadPromise ) . then ( ( [ uploadResult , uploadErr ] ) => {
309+ if ( uploadErr ) {
310+ console . error ( "Error uploading asynchronously the layer " , digest , "into main registry" ) ;
311+ return ;
312+ }
313+ if ( uploadResult === false ) {
314+ console . error ( "Layer might be too big for the registry client" , layerSize ) ;
315+ }
316+ } )
317+ ) ;
318+
319+ // Immediately return response to client (stream s1 has already started being consumed)
320+ return new Response ( s1 , {
321+ headers : {
322+ "Docker-Content-Digest" : layerResponse . digest ,
323+ "Content-Length" : `${ layerSize } ` ,
324+ } ,
325+ } ) ;
326+ }
327+
328+ // Compatibility mode: keep original behavior (return response first, then async upload)
295329 layerResponse . stream = s1 ;
296330 context . waitUntil (
297331 ( async ( ) => {
0 commit comments