1- import { $ , CryptoHasher , file , write } from "bun" ;
1+ import { $ , CryptoHasher , file , sleep , write } from "bun" ;
22import { extract } from "tar" ;
33
44import stream from "node:stream" ;
@@ -71,7 +71,7 @@ if (!(await file(tarFile).exists())) {
7171
7272 await mkdir ( imagePath ) ;
7373
74- const result = await extract ( {
74+ await extract ( {
7575 file : tarFile ,
7676 cwd : imagePath ,
7777 } ) ;
@@ -251,7 +251,6 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
251251 throw new Error ( `oci-chunk-max-length header is malformed (not a number)` ) ;
252252 }
253253
254- const reader = readableStream . getReader ( ) ;
255254 const uploadId = createUploadResponse . headers . get ( "docker-upload-uuid" ) ;
256255 if ( uploadId === null ) {
257256 throw new Error ( "Docker-Upload-UUID not defined in headers" ) ;
@@ -271,9 +270,13 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
271270 let written = 0 ;
272271 let previousReadable : ReadableLimiter | undefined ;
273272 let totalLayerSizeLeft = totalLayerSize ;
273+ const reader = readableStream . getReader ( ) ;
274+ let fail = "true" ;
275+ let failures = 0 ;
274276 while ( totalLayerSizeLeft > 0 ) {
275277 const range = `0-${ Math . min ( end , totalLayerSize ) - 1 } ` ;
276278 const current = new ReadableLimiter ( reader as ReadableStreamDefaultReader , maxToWrite , previousReadable ) ;
279+ await current . init ( ) ;
277280 const patchChunkUploadURL = parseLocation ( location ) ;
278281 // we have to do fetchNode because Bun doesn't allow setting custom Content-Length.
279282 // https://github.com/oven-sh/bun/issues/10507
@@ -284,14 +287,19 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
284287 "range" : range ,
285288 "authorization" : cred ,
286289 "content-length" : `${ Math . min ( totalLayerSizeLeft , maxToWrite ) } ` ,
290+ "x-fail" : fail ,
287291 } ) ,
288292 } ) ;
289293 if ( ! patchChunkResult . ok ) {
290- throw new Error (
291- `uploading chunk ${ patchChunkUploadURL } returned ${ patchChunkResult . status } : ${ await patchChunkResult . text ( ) } ` ,
292- ) ;
294+ previousReadable = current ;
295+ console . error ( `${ layerDigest } : Pushing ${ range } failed with ${ patchChunkResult . status } , retrying` ) ;
296+ await sleep ( 500 ) ;
297+ if ( failures ++ >= 2 ) fail = "false" ;
298+ continue ;
293299 }
294300
301+ fail = "true" ;
302+ current . ok ( ) ;
295303 const rangeResponse = patchChunkResult . headers . get ( "range" ) ;
296304 if ( rangeResponse !== range ) {
297305 throw new Error ( `unexpected Range header ${ rangeResponse } , expected ${ range } ` ) ;
@@ -308,18 +316,24 @@ async function pushLayer(layerDigest: string, readableStream: ReadableStream, to
308316 const range = `0-${ written - 1 } ` ;
309317 const uploadURL = new URL ( parseLocation ( location ) ) ;
310318 uploadURL . searchParams . append ( "digest" , layerDigest ) ;
311- const response = await fetch ( uploadURL . toString ( ) , {
312- method : "PUT" ,
313- headers : new Headers ( {
314- Range : range ,
315- Authorization : cred ,
316- } ) ,
317- } ) ;
318- if ( ! response . ok ) {
319- throw new Error ( `${ uploadURL . toString ( ) } failed with ${ response . status } : ${ await response . text ( ) } ` ) ;
319+ for ( let tries = 0 ; tries < 3 ; tries ++ ) {
320+ const response = await fetch ( uploadURL . toString ( ) , {
321+ method : "PUT" ,
322+ headers : new Headers ( {
323+ Range : range ,
324+ Authorization : cred ,
325+ } ) ,
326+ } ) ;
327+ if ( ! response . ok ) {
328+ console . error ( `${ layerDigest } : Finishing ${ range } failed with ${ response . status } , retrying` ) ;
329+ continue ;
330+ }
331+
332+ console . log ( "Pushed" , layerDigest ) ;
333+ return ;
320334 }
321335
322- console . log ( "Pushed" , layerDigest ) ;
336+ throw new Error ( `Could not push after multiple tries` ) ;
323337}
324338
325339const layersManifest = [ ] as {
0 commit comments