TUS: retry on 423_locked to try address "failed to upload chunk"

## Background
Per developer of `tus-js-client`, it is normal to occasionally encounter upload errors. The auto-retry mechanism is meant to address this.

While implementing tab-lock to prevent multiple uploads of the same file, 423_locked was used to detect this scenario. But 423_locked could also mean "the server is busy writing the chunk" (per discussion with Randy), so we kind of disabled the auto-retry mechanism accidentally.

Meanwhile, from a prior discussion with Randy, one of the chunk-writing duration took 3 minutes. Our current maximum of "retry after 15s" wouldn't help.

## Change
1. Given that tab-locking was improved recently and no longer reliant on the server error messages (we use secure storage to mark a file as locked), reverted the change to "skip retry on 409/423". This is now back to normal recommended behavior.
2. `tus-js-client` currently does not support variable retry delay, otherwise we could prolong the delay if the error was 423. Since we know it could take up to 3 minutes, and that we don't know if it's file-size dependant, just add another 30s retry and put a friendlier message asking the user to retry themselves after waiting a bit.
This commit is contained in:
infinite-persistence 2022-01-10 16:28:25 +08:00
parent e6a21dc14f
commit 6bd384b01a
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0

View file

@ -60,7 +60,7 @@ export function makeResumableUploadRequest(
const uploader = new tus.Upload(file, { const uploader = new tus.Upload(file, {
...urlOptions, ...urlOptions,
chunkSize: UPLOAD_CHUNK_SIZE_BYTE, chunkSize: UPLOAD_CHUNK_SIZE_BYTE,
retryDelays: [0, 5000, 10000, 15000], retryDelays: [0, 5000, 10000, 15000, 30000],
parallelUploads: 1, parallelUploads: 1,
storeFingerprintForResuming: false, storeFingerprintForResuming: false,
removeFingerprintOnSuccess: true, removeFingerprintOnSuccess: true,
@ -73,21 +73,25 @@ export function makeResumableUploadRequest(
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'retry' })); window.store.dispatch(doUpdateUploadProgress({ guid, status: 'retry' }));
const status = err.originalResponse ? err.originalResponse.getStatus() : 0; const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
analytics.error(`tus: retry=${uploader._retryAttempt}, status=${status}`); analytics.error(`tus: retry=${uploader._retryAttempt}, status=${status}`);
return !inStatusCategory(status, 400); return !inStatusCategory(status, 400) || status === STATUS_CONFLICT || status === STATUS_LOCKED;
}, },
onError: (err) => { onError: (err) => {
const status = err.originalResponse ? err.originalResponse.getStatus() : 0; const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
const errMsg = typeof err === 'string' ? err : err.message; const errMsg = typeof err === 'string' ? err : err.message;
if (status === STATUS_CONFLICT || status === STATUS_LOCKED || errMsg === 'file currently locked') { if (status === STATUS_CONFLICT) {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'conflict' })); window.store.dispatch(doUpdateUploadProgress({ guid, status: 'conflict' }));
// prettier-ignore reject(new Error(`${status}: concurrent upload detected.`));
reject(new Error(`${status}: concurrent upload detected. Uploading the same file from multiple tabs or windows is not allowed.`));
} else { } else {
const errToLog =
status === STATUS_LOCKED || errMsg === 'file currently locked'
? 'File is locked. Try resuming after waiting a few minutes'
: err;
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'error' })); window.store.dispatch(doUpdateUploadProgress({ guid, status: 'error' }));
reject( reject(
// $FlowFixMe - flow's constructor for Error is incorrect. // $FlowFixMe - flow's constructor for Error is incorrect.
new Error(err, { new Error(errToLog, {
cause: { cause: {
url: uploader.url, url: uploader.url,
status, status,