improve direct upload stall feedback

This commit is contained in:
134 2026-07-08 21:28:43 +08:00
parent 9575873efb
commit b49519ec56

View File

@ -10,6 +10,7 @@ const CLIENT_UPLOAD_ID_FIELD = "clientUploadId";
const DIRECT_UPLOAD_INITIATE_TIMEOUT_MS = 3 * 1000;
const DIRECT_UPLOAD_ACTION_TIMEOUT_MS = 15 * 1000;
const DIRECT_UPLOAD_ABORT_TIMEOUT_MS = 8 * 1000;
const DIRECT_UPLOAD_STALL_TIMEOUT_MS = 10 * 1000;
const XHR_UPLOAD_TOTAL_TIMEOUT_MS = 6 * 60 * 1000;
const XHR_UPLOAD_STALL_TIMEOUT_MS = 30 * 1000;
@ -336,6 +337,7 @@ async function fetchWithControlTimeout(
const controller = new AbortController();
let timeout: ReturnType<typeof setTimeout> | null = null;
let timedOut = false;
let settled = false;
const onAbort = () => {
controller.abort();
@ -344,30 +346,44 @@ async function fetchWithControlTimeout(
signal?.addEventListener("abort", onAbort, { once: true });
try {
timeout = setTimeout(() => {
timedOut = true;
controller.abort();
}, timeoutMs);
return await fetch(input, {
const request = fetch(input, {
...init,
signal: controller.signal,
}).catch((error) => {
if (signal?.aborted) {
throw createAbortError();
}
if (timedOut) {
throw createTimeoutError(
`Direct upload control request timed out after ${Math.round(
timeoutMs / 1000
)}s`
);
}
throw error;
});
} catch (error) {
if (signal?.aborted) {
throw createAbortError();
}
if (timedOut) {
throw createTimeoutError(
`Direct upload control request timed out after ${Math.round(
timeoutMs / 1000
)}s`
);
}
const timeoutPromise = new Promise<never>((_, reject) => {
timeout = setTimeout(() => {
timedOut = true;
controller.abort();
if (!settled) {
reject(
createTimeoutError(
`Direct upload control request timed out after ${Math.round(
timeoutMs / 1000
)}s`
)
);
}
}, timeoutMs);
});
throw error;
return await Promise.race([request, timeoutPromise]);
} finally {
settled = true;
if (timeout) {
clearTimeout(timeout);
}
@ -547,7 +563,8 @@ async function fetchDirectPartWithRetry(
},
maxRetries: number,
signal?: AbortSignal,
onProgress?: (loaded: number, total: number) => void
onProgress?: (loaded: number, total: number) => void,
options?: { stallTimeoutMs?: number }
) {
for (let attempt = 0; ; attempt += 1) {
try {
@ -557,6 +574,7 @@ async function fetchDirectPartWithRetry(
{
onProgress,
signal,
stallTimeoutMs: options?.stallTimeoutMs,
}
);
@ -667,7 +685,8 @@ async function uploadMultipartParts(params: {
params.signal,
(loaded, total) => {
reportPartProgress(part.partNumber, loaded, total || body.size);
}
},
{ stallTimeoutMs: DIRECT_UPLOAD_STALL_TIMEOUT_MS }
);
if (!response.ok) {
@ -731,6 +750,9 @@ async function fetchDirectUpload(
);
if (upload.mode === "single") {
emitUploadProgress(onProgress, 0, file.blob.size, "direct", {
percent: 2,
});
const response = await fetchDirectPartWithRetry(
upload.url,
{
@ -744,7 +766,8 @@ async function fetchDirectUpload(
signal,
(loaded, total) => {
emitUploadProgress(onProgress, loaded, total || file.blob.size, "direct");
}
},
{ stallTimeoutMs: DIRECT_UPLOAD_STALL_TIMEOUT_MS }
);
if (!response.ok) {
@ -774,6 +797,9 @@ async function fetchDirectUpload(
}
multipartUpload = upload;
emitUploadProgress(onProgress, 0, file.blob.size, "multipart", {
percent: 2,
});
const parts = await uploadMultipartParts({
file,
upload,