1+ import type { WriteStream } from "fs" ;
12import { createWriteStream , mkdtemp , pathExists , remove } from "fs-extra" ;
23import { tmpdir } from "os" ;
34import { delimiter , dirname , join } from "path" ;
@@ -26,6 +27,8 @@ import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently";
2627import { reportUnzipProgress } from "../common/vscode/unzip-progress" ;
2728import type { Release } from "./distribution/release" ;
2829import { ReleasesApiConsumer } from "./distribution/releases-api-consumer" ;
30+ import { createTimeoutSignal } from "../common/fetch-stream" ;
31+ import { AbortError } from "node-fetch" ;
2932
3033/**
3134 * distribution.ts
@@ -384,15 +387,21 @@ class ExtensionSpecificDistributionManager {
384387 ) ;
385388 }
386389
387- const assetStream =
388- await this . createReleasesApiConsumer ( ) . streamBinaryContentOfAsset (
389- assets [ 0 ] ,
390- ) ;
390+ const { signal, onData, dispose : disposeTimeout } = createTimeoutSignal ( 30 ) ;
391+
391392 const tmpDirectory = await mkdtemp ( join ( tmpdir ( ) , "vscode-codeql" ) ) ;
392393
394+ let archiveFile : WriteStream | undefined = undefined ;
395+
393396 try {
397+ const assetStream =
398+ await this . createReleasesApiConsumer ( ) . streamBinaryContentOfAsset (
399+ assets [ 0 ] ,
400+ signal ,
401+ ) ;
402+
394403 const archivePath = join ( tmpDirectory , "distributionDownload.zip" ) ;
395- const archiveFile = createWriteStream ( archivePath ) ;
404+ archiveFile = createWriteStream ( archivePath ) ;
396405
397406 const contentLength = assetStream . headers . get ( "content-length" ) ;
398407 const totalNumBytes = contentLength
@@ -405,12 +414,23 @@ class ExtensionSpecificDistributionManager {
405414 progressCallback ,
406415 ) ;
407416
408- await new Promise ( ( resolve , reject ) =>
417+ assetStream . body . on ( "data" , onData ) ;
418+
419+ await new Promise ( ( resolve , reject ) => {
420+ if ( ! archiveFile ) {
421+ throw new Error ( "Invariant violation: archiveFile not set" ) ;
422+ }
423+
409424 assetStream . body
410425 . pipe ( archiveFile )
411426 . on ( "finish" , resolve )
412- . on ( "error" , reject ) ,
413- ) ;
427+ . on ( "error" , reject ) ;
428+
429+ // If an error occurs on the body, we also want to reject the promise (e.g. during a timeout error).
430+ assetStream . body . on ( "error" , reject ) ;
431+ } ) ;
432+
433+ disposeTimeout ( ) ;
414434
415435 await this . bumpDistributionFolderIndex ( ) ;
416436
@@ -427,7 +447,19 @@ class ExtensionSpecificDistributionManager {
427447 )
428448 : undefined ,
429449 ) ;
450+ } catch ( e ) {
451+ if ( e instanceof AbortError ) {
452+ const thrownError = new AbortError ( "The download timed out." ) ;
453+ thrownError . stack = e . stack ;
454+ throw thrownError ;
455+ }
456+
457+ throw e ;
430458 } finally {
459+ disposeTimeout ( ) ;
460+
461+ archiveFile ?. close ( ) ;
462+
431463 await remove ( tmpDirectory ) ;
432464 }
433465 }
0 commit comments