Skip to content

Commit 8f7f7a8

Browse files
fix(request): Large file upload memory leak (#453)
1 parent dae7540 commit 8f7f7a8

File tree

4 files changed

+24
-11
lines changed

4 files changed

+24
-11
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ Error contains details field with responseBody, responseHeaders, code (only when
230230
</script>
231231
```
232232

233+
Upload abort throws an `FilestackError` with type `FilestackErrorType.ABORTED`
234+
233235
## Sentry Integration
234236

235237
If you're using [Sentry](https://sentry.io/welcome/) to monitor your application, Filestack will automatically report upload errors to Sentry, and tag them with helpful diagnostic information via [`@sentry/minimal`](https://github.com/getsentry/sentry-javascript/tree/master/packages/minimal).

src/filestack_error.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
export enum FilestackErrorType {
1919
VALIDATION = 'validation',
2020
REQUEST = 'request',
21+
ABORTED = 'aborted',
2122
OTHER = 'other',
2223
}
2324

src/lib/api/upload/uploaders/s3.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Debug from 'debug';
1818
import PQueue from 'p-queue';
1919

2020
import { FilestackError, FilestackErrorType } from './../../../../filestack_error';
21-
import { FsCancelToken, FsRequest, FsRequestError, FsResponse } from './../../../request';
21+
import { FsCancelToken, FsRequest, FsRequestError, FsRequestErrorCode, FsResponse } from './../../../request';
2222
import { shouldRetry } from './../../../request/helpers';
2323
import { filterObject, uniqueId, uniqueTime } from './../../../utils';
2424
import { File, FilePart, FilePartMetadata, FileState } from './../file';
@@ -331,7 +331,7 @@ export class S3Uploader extends UploaderAbstract {
331331
debug(`[${id}] Start request error %O`, err);
332332
this.setPayloadStatus(id, FileState.FAILED);
333333

334-
return Promise.reject(new FilestackError('Cannot upload file. Start request failed', this.getErrorDetails(err), FilestackErrorType.REQUEST));
334+
return this.rejectUpload('Cannot upload file. Start request failed', err);
335335
});
336336
}
337337

@@ -422,7 +422,7 @@ export class S3Uploader extends UploaderAbstract {
422422
}).catch(err => {
423423
this.setPayloadStatus(id, FileState.FAILED);
424424

425-
return Promise.reject(new FilestackError('Cannot get part metadata', this.getErrorDetails(err), FilestackErrorType.REQUEST));
425+
return this.rejectUpload('Cannot get part metadata', err);
426426
});
427427
}
428428

@@ -507,7 +507,7 @@ export class S3Uploader extends UploaderAbstract {
507507
return this.startPart(id, partNumber);
508508
}
509509

510-
return Promise.reject(new FilestackError('Cannot upload file part', this.getErrorDetails(err), FilestackErrorType.REQUEST));
510+
return this.rejectUpload('Cannot upload file part', err);
511511
});
512512
}
513513

@@ -616,7 +616,7 @@ export class S3Uploader extends UploaderAbstract {
616616
part = null;
617617
chunk = null;
618618

619-
return Promise.reject(new FilestackError('Cannot upload file part (FII)', this.getErrorDetails(err), FilestackErrorType.REQUEST));
619+
return this.rejectUpload('Cannot upload file part (FII)', err);
620620
});
621621
}
622622

@@ -653,7 +653,7 @@ export class S3Uploader extends UploaderAbstract {
653653
return res;
654654
})
655655
.catch(err => {
656-
return Promise.reject(new FilestackError('Cannot commit file part metadata', this.getErrorDetails(err), FilestackErrorType.REQUEST));
656+
return this.rejectUpload('Cannot commit file part metadata', err);
657657
});
658658
}
659659

@@ -729,7 +729,7 @@ export class S3Uploader extends UploaderAbstract {
729729
.catch(err => {
730730
this.setPayloadStatus(id, FileState.FAILED);
731731

732-
return Promise.reject(new FilestackError('Cannot complete file', this.getErrorDetails(err), FilestackErrorType.REQUEST));
732+
return this.rejectUpload('Cannot complete file', err);
733733
});
734734
}
735735

@@ -860,7 +860,7 @@ export class S3Uploader extends UploaderAbstract {
860860
*
861861
* @param err
862862
*/
863-
private getErrorDetails(err: FsRequestError) {
863+
private parseError(err: FsRequestError) {
864864
if (!err.response) {
865865
return {};
866866
}
@@ -871,4 +871,11 @@ export class S3Uploader extends UploaderAbstract {
871871
headers: err.response.headers,
872872
};
873873
}
874+
875+
private rejectUpload(message: string, err: FsRequestError) {
876+
if (err instanceof FsRequestError && err.code === FsRequestErrorCode.ABORTED) {
877+
return Promise.reject(new FilestackError(message, { reason: err.message }, FilestackErrorType.ABORTED));
878+
}
879+
return Promise.reject(new FilestackError(message, this.parseError(err), FilestackErrorType.REQUEST));
880+
}
874881
}

src/lib/request/adapters/xhr.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class XhrAdapter implements AdapterInterface {
7676
let cancelListener;
7777

7878
if (config.cancelToken) {
79-
cancelListener = config.cancelToken.once('cancel', (reason) => {
79+
cancelListener = (reason) => {
8080
/* istanbul ignore next: if request is done cancel token should not throw any error */
8181
if (request) {
8282
request.abort();
@@ -85,7 +85,9 @@ export class XhrAdapter implements AdapterInterface {
8585

8686
debug('Request canceled by user %s, config: %O', reason, config);
8787
return reject(new FsRequestError(`Request aborted. Reason: ${reason}`, config, null, FsRequestErrorCode.ABORTED));
88-
});
88+
};
89+
90+
config.cancelToken.once('cancel', cancelListener);
8991
}
9092

9193
request.onreadystatechange = async () => {
@@ -123,7 +125,8 @@ export class XhrAdapter implements AdapterInterface {
123125

124126
// clear cancel token to avoid memory leak
125127
if (config.cancelToken) {
126-
config.cancelToken.removeListener(cancelListener);
128+
config.cancelToken.removeListener('cancel', cancelListener);
129+
cancelListener = null;
127130
}
128131

129132
return resolve(response);

0 commit comments

Comments
 (0)