mirror of https://github.com/actions/toolkit
@actions/cache: using concurrent download whenever server supported (via `Accept-Ranges` header)
parent
6dd369c0e6
commit
732400a59e
|
@ -13,12 +13,12 @@ import * as utils from './cacheUtils'
|
|||
import {CompressionMethod} from './constants'
|
||||
import {
|
||||
ArtifactCacheEntry,
|
||||
InternalCacheOptions,
|
||||
ArtifactCacheList,
|
||||
CommitCacheRequest,
|
||||
ReserveCacheRequest,
|
||||
ReserveCacheResponse,
|
||||
InternalCacheOptions,
|
||||
ITypedResponseWithError,
|
||||
ArtifactCacheList
|
||||
ReserveCacheRequest,
|
||||
ReserveCacheResponse
|
||||
} from './contracts'
|
||||
import {
|
||||
downloadCacheHttpClient,
|
||||
|
@ -27,9 +27,9 @@ import {
|
|||
} from './downloadUtils'
|
||||
import {
|
||||
DownloadOptions,
|
||||
UploadOptions,
|
||||
getDownloadOptions,
|
||||
getUploadOptions
|
||||
getUploadOptions,
|
||||
UploadOptions
|
||||
} from '../options'
|
||||
import {
|
||||
isSuccessStatusCode,
|
||||
|
@ -179,22 +179,45 @@ export async function downloadCache(
|
|||
if (archiveUrl.hostname.endsWith('.blob.core.windows.net')) {
|
||||
if (downloadOptions.useAzureSdk) {
|
||||
// Use Azure storage SDK to download caches hosted on Azure to improve speed and reliability.
|
||||
await downloadCacheStorageSDK(
|
||||
return await downloadCacheStorageSDK(
|
||||
archiveLocation,
|
||||
archivePath,
|
||||
downloadOptions
|
||||
)
|
||||
} else if (downloadOptions.concurrentBlobDownloads) {
|
||||
// Use concurrent implementation with HttpClient to work around blob SDK issue
|
||||
await downloadCacheHttpClientConcurrent(
|
||||
archiveLocation,
|
||||
archivePath,
|
||||
downloadOptions
|
||||
)
|
||||
} else {
|
||||
// Otherwise, download using the Actions http-client.
|
||||
await downloadCacheHttpClient(archiveLocation, archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
let acceptRange = false
|
||||
let contentLength = -1
|
||||
// Determine partial file downloads is supported by server
|
||||
// via `Accept-Ranges: bytes` response header.
|
||||
try {
|
||||
const httpClient = new HttpClient('actions/cache', undefined, {
|
||||
socketTimeout: downloadOptions.timeoutInMs,
|
||||
keepAlive: true
|
||||
})
|
||||
|
||||
const res = await retryHttpClientResponse(
|
||||
'downloadCacheMetadata',
|
||||
async () => await httpClient.request('HEAD', archiveLocation, null, {})
|
||||
)
|
||||
|
||||
acceptRange = res.message.headers['Accept-Ranges'] === 'bytes'
|
||||
|
||||
const lengthHeader = res.message.headers['Content-Length']
|
||||
contentLength = parseInt(lengthHeader)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (acceptRange && contentLength > 0) {
|
||||
// Use concurrent implementation with HttpClient to work around blob SDK issue
|
||||
await downloadCacheHttpClientConcurrent(
|
||||
archiveLocation,
|
||||
archivePath,
|
||||
contentLength,
|
||||
downloadOptions
|
||||
)
|
||||
} else {
|
||||
await downloadCacheHttpClient(archiveLocation, archivePath)
|
||||
}
|
||||
|
|
|
@ -208,10 +208,13 @@ export async function downloadCacheHttpClient(
|
|||
*
|
||||
* @param archiveLocation the URL for the cache
|
||||
* @param archivePath the local path where the cache is saved
|
||||
* @param contentLength
|
||||
* @param options
|
||||
*/
|
||||
export async function downloadCacheHttpClientConcurrent(
|
||||
archiveLocation: string,
|
||||
archivePath: fs.PathLike,
|
||||
contentLength: number,
|
||||
options: DownloadOptions
|
||||
): Promise<void> {
|
||||
const archiveDescriptor = await fs.promises.open(archivePath, 'w')
|
||||
|
@ -220,29 +223,14 @@ export async function downloadCacheHttpClientConcurrent(
|
|||
keepAlive: true
|
||||
})
|
||||
try {
|
||||
const res = await retryHttpClientResponse(
|
||||
'downloadCacheMetadata',
|
||||
async () => await httpClient.request('HEAD', archiveLocation, null, {})
|
||||
)
|
||||
|
||||
const lengthHeader = res.message.headers['content-length']
|
||||
if (lengthHeader === undefined || lengthHeader === null) {
|
||||
throw new Error('Content-Length not found on blob response')
|
||||
}
|
||||
|
||||
const length = parseInt(lengthHeader)
|
||||
if (Number.isNaN(length)) {
|
||||
throw new Error(`Could not interpret Content-Length: ${length}`)
|
||||
}
|
||||
|
||||
const downloads: {
|
||||
offset: number
|
||||
promiseGetter: () => Promise<DownloadSegment>
|
||||
}[] = []
|
||||
const blockSize = 4 * 1024 * 1024
|
||||
|
||||
for (let offset = 0; offset < length; offset += blockSize) {
|
||||
const count = Math.min(blockSize, length - offset)
|
||||
for (let offset = 0; offset < contentLength; offset += blockSize) {
|
||||
const count = Math.min(blockSize, contentLength - offset)
|
||||
downloads.push({
|
||||
offset,
|
||||
promiseGetter: async () => {
|
||||
|
@ -260,7 +248,7 @@ export async function downloadCacheHttpClientConcurrent(
|
|||
downloads.reverse()
|
||||
let actives = 0
|
||||
let bytesDownloaded = 0
|
||||
const progress = new DownloadProgress(length)
|
||||
const progress = new DownloadProgress(contentLength)
|
||||
progress.startDisplayTimer()
|
||||
const progressFn = progress.onProgress()
|
||||
|
||||
|
|
Loading…
Reference in New Issue