Handle abort signal in SocketManager
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
2e82be1f3b
commit
fbdb61be52
3 changed files with 64 additions and 29 deletions
|
@ -477,6 +477,7 @@ export class BackupsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let stream: Readable;
|
let stream: Readable;
|
||||||
|
try {
|
||||||
if (ephemeralKey == null) {
|
if (ephemeralKey == null) {
|
||||||
stream = await this.api.download({
|
stream = await this.api.download({
|
||||||
downloadOffset,
|
downloadOffset,
|
||||||
|
@ -490,6 +491,12 @@ export class BackupsService {
|
||||||
abortSignal: controller.signal,
|
abortSignal: controller.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (controller.signal.aborted) {
|
if (controller.signal.aborted) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import EventListener from 'events';
|
||||||
|
|
||||||
import { AbortableProcess } from '../util/AbortableProcess';
|
import { AbortableProcess } from '../util/AbortableProcess';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { explodePromise } from '../util/explodePromise';
|
||||||
import {
|
import {
|
||||||
BackOff,
|
BackOff,
|
||||||
EXTENDED_FIBONACCI_TIMEOUTS,
|
EXTENDED_FIBONACCI_TIMEOUTS,
|
||||||
|
@ -421,7 +422,7 @@ export class SocketManager extends EventListener {
|
||||||
const { path } = URL.parse(url);
|
const { path } = URL.parse(url);
|
||||||
strictAssert(path, "Fetch can't have empty path");
|
strictAssert(path, "Fetch can't have empty path");
|
||||||
|
|
||||||
const { method = 'GET', body, timeout } = init;
|
const { method = 'GET', body, timeout, signal } = init;
|
||||||
|
|
||||||
let bodyBytes: Uint8Array | undefined;
|
let bodyBytes: Uint8Array | undefined;
|
||||||
if (body === undefined) {
|
if (body === undefined) {
|
||||||
|
@ -436,13 +437,26 @@ export class SocketManager extends EventListener {
|
||||||
throw new Error(`Unsupported body type: ${typeof body}`);
|
throw new Error(`Unsupported body type: ${typeof body}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource.sendRequest({
|
const { promise: abortPromise, reject } = explodePromise<Response>();
|
||||||
|
|
||||||
|
const onAbort = () => reject(new Error('Aborted'));
|
||||||
|
const cleanup = () => signal?.removeEventListener('abort', onAbort);
|
||||||
|
|
||||||
|
signal?.addEventListener('abort', onAbort, { once: true });
|
||||||
|
|
||||||
|
const responsePromise = resource.sendRequest({
|
||||||
verb: method,
|
verb: method,
|
||||||
path,
|
path,
|
||||||
body: bodyBytes,
|
body: bodyBytes,
|
||||||
headers: Array.from(headers.entries()),
|
headers: Array.from(headers.entries()),
|
||||||
timeout,
|
timeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await Promise.race([responsePromise, abortPromise]);
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerRequestHandler(handler: IRequestHandler): void {
|
public registerRequestHandler(handler: IRequestHandler): void {
|
||||||
|
|
|
@ -68,7 +68,7 @@ import type {
|
||||||
import { handleStatusCode, translateError } from './Utils';
|
import { handleStatusCode, translateError } from './Utils';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { maybeParseUrl, urlPathFromComponents } from '../util/url';
|
import { maybeParseUrl, urlPathFromComponents } from '../util/url';
|
||||||
import { SECOND } from '../util/durations';
|
import { HOUR, MINUTE, SECOND } from '../util/durations';
|
||||||
import { safeParseNumber } from '../util/numbers';
|
import { safeParseNumber } from '../util/numbers';
|
||||||
import { isStagingServer } from '../util/isStagingServer';
|
import { isStagingServer } from '../util/isStagingServer';
|
||||||
import type { IWebSocketResource } from './WebsocketResources';
|
import type { IWebSocketResource } from './WebsocketResources';
|
||||||
|
@ -165,8 +165,8 @@ function _validateResponse(response: any, schema: any) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIVE_MINUTES = 5 * durations.MINUTE;
|
const FIVE_MINUTES = 5 * MINUTE;
|
||||||
const GET_ATTACHMENT_CHUNK_TIMEOUT = 10 * durations.SECOND;
|
const GET_ATTACHMENT_CHUNK_TIMEOUT = 10 * SECOND;
|
||||||
|
|
||||||
type AgentCacheType = {
|
type AgentCacheType = {
|
||||||
[name: string]: {
|
[name: string]: {
|
||||||
|
@ -545,7 +545,12 @@ async function _retryAjax(
|
||||||
try {
|
try {
|
||||||
return await _promiseAjax(url, options);
|
return await _promiseAjax(url, options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof HTTPError && e.code === -1 && count < limit) {
|
if (
|
||||||
|
e instanceof HTTPError &&
|
||||||
|
e.code === -1 &&
|
||||||
|
count < limit &&
|
||||||
|
!options.abortSignal?.aborted
|
||||||
|
) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(_retryAjax(url, options, limit, count));
|
resolve(_retryAjax(url, options, limit, count));
|
||||||
|
@ -2242,26 +2247,31 @@ export function initialize({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTransferArchive({
|
async function getTransferArchive({
|
||||||
timeout = durations.HOUR,
|
timeout = HOUR,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
}: GetTransferArchiveOptionsType): Promise<TransferArchiveType> {
|
}: GetTransferArchiveOptionsType): Promise<TransferArchiveType> {
|
||||||
const timeoutTime = Date.now() + timeout;
|
const timeoutTime = Date.now() + timeout;
|
||||||
|
|
||||||
const urlParameters = timeout
|
|
||||||
? `?timeout=${encodeURIComponent(Math.round(timeout / SECOND))}`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
let remainingTime: number;
|
let remainingTime: number;
|
||||||
do {
|
do {
|
||||||
remainingTime = Math.max(timeoutTime - Date.now(), 0);
|
remainingTime = Math.max(timeoutTime - Date.now(), 0);
|
||||||
|
|
||||||
|
const requestTimeoutInSecs = Math.round(
|
||||||
|
Math.min(remainingTime, 5 * MINUTE) / SECOND
|
||||||
|
);
|
||||||
|
|
||||||
|
const urlParameters = timeout
|
||||||
|
? `?timeout=${encodeURIComponent(requestTimeoutInSecs)}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const { data, response }: JSONWithDetailsType = await _ajax({
|
const { data, response }: JSONWithDetailsType = await _ajax({
|
||||||
call: 'transferArchive',
|
call: 'transferArchive',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'jsonwithdetails',
|
responseType: 'jsonwithdetails',
|
||||||
urlParameters,
|
urlParameters,
|
||||||
timeout: remainingTime,
|
// Add a bit of leeway to let server respond properly
|
||||||
|
timeout: requestTimeoutInSecs + 15 * SECOND,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2274,6 +2284,10 @@ export function initialize({
|
||||||
'Invalid transfer archive status code'
|
'Invalid transfer archive status code'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (abortSignal?.aborted) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Timed out, see if we can retry
|
// Timed out, see if we can retry
|
||||||
} while (!timeout || remainingTime != null);
|
} while (!timeout || remainingTime != null);
|
||||||
|
|
||||||
|
@ -3049,8 +3063,8 @@ export function initialize({
|
||||||
startDayInMs,
|
startDayInMs,
|
||||||
endDayInMs,
|
endDayInMs,
|
||||||
}: GetBackupCredentialsOptionsType) {
|
}: GetBackupCredentialsOptionsType) {
|
||||||
const startDayInSeconds = startDayInMs / durations.SECOND;
|
const startDayInSeconds = startDayInMs / SECOND;
|
||||||
const endDayInSeconds = endDayInMs / durations.SECOND;
|
const endDayInSeconds = endDayInMs / SECOND;
|
||||||
const res = await _ajax({
|
const res = await _ajax({
|
||||||
call: 'getBackupCredentials',
|
call: 'getBackupCredentials',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
|
@ -3616,7 +3630,7 @@ export function initialize({
|
||||||
// Upload stickers
|
// Upload stickers
|
||||||
const queue = new PQueue({
|
const queue = new PQueue({
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: durations.MINUTE * 30,
|
timeout: MINUTE * 30,
|
||||||
throwOnTimeout: true,
|
throwOnTimeout: true,
|
||||||
});
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
@ -4060,8 +4074,8 @@ export function initialize({
|
||||||
startDayInMs,
|
startDayInMs,
|
||||||
endDayInMs,
|
endDayInMs,
|
||||||
}: GetGroupCredentialsOptionsType): Promise<GetGroupCredentialsResultType> {
|
}: GetGroupCredentialsOptionsType): Promise<GetGroupCredentialsResultType> {
|
||||||
const startDayInSeconds = startDayInMs / durations.SECOND;
|
const startDayInSeconds = startDayInMs / SECOND;
|
||||||
const endDayInSeconds = endDayInMs / durations.SECOND;
|
const endDayInSeconds = endDayInMs / SECOND;
|
||||||
const response = (await _ajax({
|
const response = (await _ajax({
|
||||||
call: 'getGroupCredentials',
|
call: 'getGroupCredentials',
|
||||||
urlParameters:
|
urlParameters:
|
||||||
|
|
Loading…
Reference in a new issue