Add stalling for fileUtils

This commit is contained in:
Finn Dittmar 2026-03-09 18:56:39 +01:00
parent 0506b75ef2
commit f86258cd13
No known key found for this signature in database
GPG key ID: A630219F715A1D1E

View file

@ -297,6 +297,8 @@ module.exports.getFilePathItemFromFileUpdate = (fileUpdate) => {
*/ */
module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => { module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const DOWNLOAD_STALL_TIMEOUT_MS = 60000
Logger.debug(`[fileUtils] Downloading file to ${filepath}`) Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
axios({ axios({
url, url,
@ -310,9 +312,42 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
httpsAgent: global.DisableSsrfRequestFilter?.(url) ? null : ssrfFilter(url) httpsAgent: global.DisableSsrfRequestFilter?.(url) ? null : ssrfFilter(url)
}) })
.then((response) => { .then((response) => {
let isSettled = false
let downloadStallWatchdog = null
const clearDownloadStallWatchdog = () => {
if (downloadStallWatchdog) {
clearTimeout(downloadStallWatchdog)
downloadStallWatchdog = null
}
}
const scheduleDownloadStallWatchdog = () => {
clearDownloadStallWatchdog()
downloadStallWatchdog = setTimeout(() => {
Logger.error(`[fileUtils] File "${Path.basename(filepath)}" download stalled (no data for ${DOWNLOAD_STALL_TIMEOUT_MS}ms)`)
const timeoutError = new Error(`Download stalled for ${DOWNLOAD_STALL_TIMEOUT_MS}ms`)
response.data.destroy(timeoutError)
writer.destroy(timeoutError)
settle(timeoutError)
}, DOWNLOAD_STALL_TIMEOUT_MS)
}
const settle = (error = null) => {
if (isSettled) return
isSettled = true
clearDownloadStallWatchdog()
if (error) {
reject(error)
} else {
resolve()
}
}
// Validate content type // Validate content type
if (contentTypeFilter && !contentTypeFilter?.(response.headers?.['content-type'])) { if (contentTypeFilter && !contentTypeFilter?.(response.headers?.['content-type'])) {
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`)) response.data.destroy(new Error('Invalid content type'))
return settle(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
} }
const totalSize = parseInt(response.headers['content-length'], 10) const totalSize = parseInt(response.headers['content-length'], 10)
@ -322,8 +357,11 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
const writer = fs.createWriteStream(filepath) const writer = fs.createWriteStream(filepath)
response.data.pipe(writer) response.data.pipe(writer)
scheduleDownloadStallWatchdog()
let lastProgress = 0 let lastProgress = 0
response.data.on('data', (chunk) => { response.data.on('data', (chunk) => {
scheduleDownloadStallWatchdog()
downloadedSize += chunk.length downloadedSize += chunk.length
const progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0 const progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
if (progress >= lastProgress + 5) { if (progress >= lastProgress + 5) {
@ -332,8 +370,11 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
} }
}) })
writer.on('finish', resolve) response.data.on('error', (err) => {
writer.on('error', reject) settle(err)
})
writer.on('finish', () => settle())
writer.on('error', (err) => settle(err))
}) })
.catch((err) => { .catch((err) => {
Logger.error(`[fileUtils] Failed to download file "${filepath}"`, err) Logger.error(`[fileUtils] Failed to download file "${filepath}"`, err)