Add "clean up timer if necessary" utility

This commit is contained in:
Evan Hahn 2022-02-25 12:37:15 -06:00 committed by GitHub
parent c2a65306e2
commit 052a8e65e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 150 additions and 137 deletions

View File

@ -84,6 +84,7 @@ import {
parseSignalHttpsLink,
rewriteSignalHrefsIfNecessary,
} from '../ts/util/sgnlHref';
import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary';
import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow';
import {
getTitleBarVisibility,
@ -1698,9 +1699,7 @@ async function requestShutdown() {
if (error) {
return reject(error);
}
if (timeout) {
clearTimeout(timeout);
}
clearTimeoutIfNecessary(timeout);
resolve();
});

View File

@ -1,8 +1,9 @@
// Copyright 2018-2021 Signal Messenger, LLC
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import EventEmitter from 'events';
import * as log from './logging/log';
import { clearTimeoutIfNecessary } from './util/clearTimeoutIfNecessary';
const POLL_INTERVAL_MS = 5 * 1000;
const IDLE_THRESHOLD_MS = 20;
@ -31,10 +32,8 @@ export class IdleDetector extends EventEmitter {
delete this.handle;
}
if (this.timeoutId) {
clearTimeout(this.timeoutId);
delete this.timeoutId;
}
clearTimeoutIfNecessary(this.timeoutId);
delete this.timeoutId;
}
private scheduleNextCallback() {

View File

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// `ChallengeHandler` is responsible for:
@ -17,6 +17,7 @@ import { assert } from './util/assert';
import { isNotNil } from './util/isNotNil';
import { isOlderThan } from './util/timestamp';
import { parseRetryAfter } from './util/parseRetryAfter';
import { clearTimeoutIfNecessary } from './util/clearTimeoutIfNecessary';
import { getEnvironment, Environment } from './environment';
import type { StorageInterface } from './types/Storage.d';
import { HTTPError } from './textsecure/Errors';
@ -250,7 +251,7 @@ export class ChallengeHandler {
const waitTime = Math.max(0, error.retryAfter - Date.now());
const oldTimer = this.retryTimers.get(message.id);
if (oldTimer) {
clearTimeout(oldTimer);
clearTimeoutIfNecessary(oldTimer);
}
this.retryTimers.set(
message.id,
@ -305,9 +306,7 @@ export class ChallengeHandler {
const timer = this.retryTimers.get(message.id);
this.retryTimers.delete(message.id);
if (timer) {
clearTimeout(timer);
}
clearTimeoutIfNecessary(timer);
await this.persist();
}

View File

@ -6,6 +6,7 @@ import type { ActiveCallType } from '../types/Calling';
import { CallMode, GroupCallConnectionState } from '../types/Calling';
import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { CallingToast, DEFAULT_LIFETIME } from './CallingToast';
type PropsType = {
@ -126,9 +127,7 @@ export const CallingToastManager: React.FC<PropsType> = props => {
useEffect(() => {
if (toast) {
if (toast.type === 'dismissable') {
if (timeoutRef && timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
clearTimeoutIfNecessary(timeoutRef.current);
timeoutRef.current = setTimeout(dismissToast, DEFAULT_LIFETIME);
}
@ -136,9 +135,7 @@ export const CallingToastManager: React.FC<PropsType> = props => {
}
return () => {
if (timeoutRef && timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
clearTimeoutIfNecessary(timeoutRef.current);
};
}, [dismissToast, setToastMessage, timeoutRef, toast]);

View File

@ -1,4 +1,4 @@
// Copyright 2020-2021 Signal Messenger, LLC
// Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect } from 'react';
@ -9,6 +9,7 @@ import type { LocalizerType } from '../types/Util';
import { SocketStatus } from '../types/SocketStatus';
import type { NetworkStateType } from '../state/ducks/network';
import type { WidthBreakpoint } from './_util';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
const FIVE_SECONDS = 5 * 1000;
@ -44,9 +45,7 @@ export const DialogNetworkStatus = ({
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
clearTimeoutIfNecessary(timeout);
};
}, [hasNetworkDialog, isConnecting, setIsConnecting]);

View File

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { KeyboardEvent, MouseEvent, ReactNode } from 'react';
@ -6,6 +6,7 @@ import React, { memo, useEffect } from 'react';
import classNames from 'classnames';
import { createPortal } from 'react-dom';
import { useRestoreFocus } from '../hooks/useRestoreFocus';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
export type PropsType = {
autoDismissDisabled?: boolean;
@ -54,9 +55,7 @@ export const Toast = memo(
const timeoutId = setTimeout(onClose, timeout);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
clearTimeoutIfNecessary(timeoutId);
};
}, [autoDismissDisabled, onClose, root, timeout]);

View File

@ -1,10 +1,11 @@
// Copyright 2018-2021 Signal Messenger, LLC
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import classNames from 'classnames';
import { getIncrement, getTimerBucket } from '../../util/timer';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
export type Props = {
withImageNoCaption?: boolean;
@ -40,9 +41,7 @@ export class ExpireTimer extends React.Component<Props> {
}
public override componentWillUnmount(): void {
if (this.interval) {
clearInterval(this.interval);
}
clearTimeoutIfNecessary(this.interval);
}
public override render(): JSX.Element {

View File

@ -1,4 +1,4 @@
// Copyright 2018-2021 Signal Messenger, LLC
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { RefObject } from 'react';
@ -58,6 +58,7 @@ import {
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
import { getIncrement } from '../../util/timer';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
import { isFileDangerous } from '../../util/isFileDangerous';
import { missingCaseError } from '../../util/missingCaseError';
import type {
@ -451,18 +452,10 @@ export class Message extends React.PureComponent<Props, State> {
}
public override componentWillUnmount(): void {
if (this.selectedTimeout) {
clearTimeout(this.selectedTimeout);
}
if (this.expirationCheckInterval) {
clearInterval(this.expirationCheckInterval);
}
if (this.expiredTimeout) {
clearTimeout(this.expiredTimeout);
}
if (this.deleteForEveryoneTimeout) {
clearTimeout(this.deleteForEveryoneTimeout);
}
clearTimeoutIfNecessary(this.selectedTimeout);
clearTimeoutIfNecessary(this.expirationCheckInterval);
clearTimeoutIfNecessary(this.expiredTimeout);
clearTimeoutIfNecessary(this.deleteForEveryoneTimeout);
this.toggleReactionViewer(true);
this.toggleReactionPicker(true);
}

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import moment from 'moment';
import { formatTime } from '../../util/timestamp';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
import type { LocalizerType } from '../../types/Util';
@ -42,9 +43,7 @@ export class MessageTimestamp extends React.Component<Props> {
}
public override componentWillUnmount(): void {
if (this.interval) {
clearInterval(this.interval);
}
clearTimeoutIfNecessary(this.interval);
}
public override render(): JSX.Element | null {

View File

@ -18,6 +18,7 @@ import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
import { assert } from '../../util/assert';
import { missingCaseError } from '../../util/missingCaseError';
import { createRefMerger } from '../../util/refMerger';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
import { WidthBreakpoint } from '../_util';
import type { PropsActions as MessageActionsType } from './Message';
@ -483,9 +484,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
}
this.setState({ hasRecentlyScrolled: true });
if (this.hasRecentlyScrolledTimeout) {
clearTimeout(this.hasRecentlyScrolledTimeout);
}
clearTimeoutIfNecessary(this.hasRecentlyScrolledTimeout);
this.hasRecentlyScrolledTimeout = setTimeout(() => {
this.setState({ hasRecentlyScrolled: false });
}, 3000);
@ -549,10 +548,8 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
const atTop = scrollTop <= AT_TOP_THRESHOLD;
const loadCountdownStart = atTop && !haveOldest ? Date.now() : undefined;
if (this.loadCountdownTimeout) {
clearTimeout(this.loadCountdownTimeout);
this.loadCountdownTimeout = null;
}
clearTimeoutIfNecessary(this.loadCountdownTimeout);
this.loadCountdownTimeout = null;
if (isNumber(loadCountdownStart)) {
this.loadCountdownTimeout = setTimeout(
this.loadOlderMessages,
@ -746,10 +743,8 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
const { haveOldest, isLoadingMessages, items, loadOlderMessages } =
this.props;
if (this.loadCountdownTimeout) {
clearTimeout(this.loadCountdownTimeout);
this.loadCountdownTimeout = null;
}
clearTimeoutIfNecessary(this.loadCountdownTimeout);
this.loadCountdownTimeout = null;
if (isLoadingMessages || haveOldest || !items || items.length < 1) {
return;
@ -962,9 +957,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
window.unregisterForActive(this.updateWithVisibleRows);
if (delayedPeekTimeout) {
clearTimeout(delayedPeekTimeout);
}
clearTimeoutIfNecessary(delayedPeekTimeout);
}
public override componentDidUpdate(

View File

@ -1,4 +1,4 @@
// Copyright 2019-2021 Signal Messenger, LLC
// Copyright 2019-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { isNumber, omit } from 'lodash';
@ -6,6 +6,7 @@ import { v4 as getGuid } from 'uuid';
import dataInterface from '../sql/Client';
import * as durations from '../util/durations';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { downloadAttachment } from '../util/downloadAttachment';
import * as Bytes from '../Bytes';
import type {
@ -67,10 +68,8 @@ export async function stop(): Promise<void> {
logger.info('attachment_downloads/stop: disabling');
}
enabled = false;
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
clearTimeoutIfNecessary(timeout);
timeout = null;
}
export async function addJob(
@ -115,10 +114,8 @@ export async function addJob(
}
async function _tick(): Promise<void> {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
clearTimeoutIfNecessary(timeout);
timeout = null;
_maybeStartJob();
timeout = setTimeout(_tick, TICK_INTERVAL);

View File

@ -18,6 +18,7 @@ import type {
} from '../model-types.d';
import { getInitials } from '../util/getInitials';
import { normalizeUuid } from '../util/normalizeUuid';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import type { AttachmentType } from '../types/Attachment';
import { isGIF } from '../types/Attachment';
import type { CallHistoryDetailsType } from '../types/Calling';
@ -1032,9 +1033,7 @@ export class ConversationModel extends window.Backbone
}
setTypingRefreshTimer(): void {
if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer);
}
clearTimeoutIfNecessary(this.typingRefreshTimer);
this.typingRefreshTimer = setTimeout(
this.onTypingRefreshTimeout.bind(this),
10 * 1000
@ -1050,9 +1049,7 @@ export class ConversationModel extends window.Backbone
}
setTypingPauseTimer(): void {
if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer);
}
clearTimeoutIfNecessary(this.typingPauseTimer);
this.typingPauseTimer = setTimeout(
this.onTypingPauseTimeout.bind(this),
3 * 1000
@ -1067,14 +1064,10 @@ export class ConversationModel extends window.Backbone
}
clearTypingTimers(): void {
if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer);
this.typingPauseTimer = null;
}
if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer);
this.typingRefreshTimer = null;
}
clearTimeoutIfNecessary(this.typingPauseTimer);
this.typingPauseTimer = null;
clearTimeoutIfNecessary(this.typingRefreshTimer);
this.typingRefreshTimer = null;
}
async fetchLatestGroupV2Data(
@ -4914,10 +4907,8 @@ export class ConversationModel extends window.Backbone
}
startMuteTimer({ viaStorageServiceSync = false } = {}): void {
if (this.muteTimer !== undefined) {
clearTimeout(this.muteTimer);
this.muteTimer = undefined;
}
clearTimeoutIfNecessary(this.muteTimer);
this.muteTimer = undefined;
const muteExpiresAt = this.get('muteExpiresAt');
if (isNumber(muteExpiresAt) && muteExpiresAt < Number.MAX_SAFE_INTEGER) {

View File

@ -75,6 +75,7 @@ import { dropNull, shallowDropNull } from '../util/dropNull';
import { getOwn } from '../util/getOwn';
import { isNormalNumber } from '../util/isNormalNumber';
import * as durations from '../util/durations';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { handleMessageSend } from '../util/handleMessageSend';
import { fetchMembershipProof, getMembershipList } from '../groups';
import { wrapWithSyncMessageSend } from '../util/wrapWithSyncMessageSend';
@ -1228,10 +1229,8 @@ export class CallingClass {
}
private stopDeviceReselectionTimer() {
if (this.deviceReselectionTimer) {
clearInterval(this.deviceReselectionTimer);
this.deviceReselectionTimer = undefined;
}
clearTimeoutIfNecessary(this.deviceReselectionTimer);
this.deviceReselectionTimer = undefined;
}
private mediaDeviceSettingsEqual(

View File

@ -0,0 +1,51 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as sinon from 'sinon';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
describe('clearTimeoutIfNecessary', () => {
let sandbox: sinon.SinonSandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
it('does nothing if passed `null` or `undefined`', () => {
sandbox.stub(global, 'clearTimeout');
sandbox.stub(global, 'clearInterval');
clearTimeoutIfNecessary(undefined);
clearTimeoutIfNecessary(null);
sinon.assert.notCalled(global.clearTimeout as sinon.SinonSpy);
sinon.assert.notCalled(global.clearInterval as sinon.SinonSpy);
});
it('clears timeouts', async () => {
const clock = sandbox.useFakeTimers();
const fn = sinon.fake();
const timeout = setTimeout(fn, 123);
clearTimeoutIfNecessary(timeout);
await clock.tickAsync(9999);
sinon.assert.notCalled(fn);
});
it('clears intervals', async () => {
const clock = sandbox.useFakeTimers();
const fn = sinon.fake();
const interval = setInterval(fn, 123);
clearTimeoutIfNecessary(interval);
await clock.tickAsync(9999);
sinon.assert.notCalled(fn);
});
});

View File

@ -45,6 +45,7 @@ import { dropNull } from '../util/dropNull';
import { normalizeUuid } from '../util/normalizeUuid';
import { normalizeNumber } from '../util/normalizeNumber';
import { parseIntOrThrow } from '../util/parseIntOrThrow';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { Zone } from '../util/Zone';
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
import type { DownloadedAttachmentType } from '../types/Attachment';
@ -756,10 +757,8 @@ export default class MessageReceiver
}
private clearRetryTimeout(): void {
if (this.retryCachedTimeout) {
clearInterval(this.retryCachedTimeout);
this.retryCachedTimeout = undefined;
}
clearTimeoutIfNecessary(this.retryCachedTimeout);
this.retryCachedTimeout = undefined;
}
private maybeScheduleRetryTimeout(): void {

View File

@ -1,7 +1,8 @@
// Copyright 2017-2020 Signal Messenger, LLC
// Copyright 2017-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as durations from '../util/durations';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { UUIDKind } from '../types/UUID';
import * as log from '../logging/log';
@ -38,9 +39,7 @@ export class RotateSignedPreKeyListener {
waitTime = 0;
}
if (this.timeout !== undefined) {
clearTimeout(this.timeout);
}
clearTimeoutIfNecessary(this.timeout);
this.timeout = setTimeout(() => this.runWhenOnline(), waitTime);
}

View File

@ -1,7 +1,8 @@
// Copyright 2020 Signal Messenger, LLC
// Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as durations from '../util/durations';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { explodePromise } from '../util/explodePromise';
import { toLogFormat } from '../types/errors';
import * as log from '../logging/log';
@ -66,10 +67,8 @@ export default function createTaskWithTimeout<T, Args extends Array<unknown>>(
};
const stopTimer = () => {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
clearTimeoutIfNecessary(timer);
timer = undefined;
};
const entry: TaskType = {

View File

@ -1,10 +1,11 @@
// Copyright 2019-2021 Signal Messenger, LLC
// Copyright 2019-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import PQueue from 'p-queue';
import { sleep } from './sleep';
import * as log from '../logging/log';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
declare global {
// We want to extend `window`'s properties, so we need an interface.
@ -51,10 +52,8 @@ export function createBatcher<ItemType>(
});
function _kickBatchOff() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
clearTimeoutIfNecessary(timeout);
timeout = null;
const itemsRef = items;
items = [];

View File

@ -0,0 +1,10 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export function clearTimeoutIfNecessary(
timeout: undefined | null | ReturnType<typeof setTimeout>
): void {
if (timeout) {
clearTimeout(timeout);
}
}

View File

@ -1,9 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Readable } from 'stream';
import * as Bytes from '../Bytes';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
import { explodePromise } from './explodePromise';
export type OptionsType = Readonly<{
@ -25,10 +26,8 @@ export function getStreamWithTimeout(
let timer: NodeJS.Timeout | undefined;
const clearTimer = () => {
if (timer !== undefined) {
clearTimeout(timer);
timer = undefined;
}
clearTimeoutIfNecessary(timer);
timer = undefined;
};
const reset = () => {

View File

@ -1,7 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../logging/log';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
export async function longRunningTaskWrapper<T>({
name,
@ -39,10 +40,8 @@ export async function longRunningTaskWrapper<T>({
const result = await task();
log.info(`longRunningTaskWrapper/${idLog}: Task completed successfully`);
if (progressTimeout) {
clearTimeout(progressTimeout);
progressTimeout = undefined;
}
clearTimeoutIfNecessary(progressTimeout);
progressTimeout = undefined;
if (progressView) {
const now = Date.now();
if (spinnerStart && now - spinnerStart < ONE_SECOND) {
@ -62,10 +61,8 @@ export async function longRunningTaskWrapper<T>({
error && error.stack ? error.stack : error
);
if (progressTimeout) {
clearTimeout(progressTimeout);
progressTimeout = undefined;
}
clearTimeoutIfNecessary(progressTimeout);
progressTimeout = undefined;
if (progressView) {
progressView.remove();
progressView = undefined;

View File

@ -1,10 +1,11 @@
// Copyright 2019-2021 Signal Messenger, LLC
// Copyright 2019-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import PQueue from 'p-queue';
import { sleep } from './sleep';
import * as log from '../logging/log';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
declare global {
// We want to extend `window`'s properties, so we need an interface.
@ -119,10 +120,8 @@ export function createWaitBatcher<ItemType>(
}, options.wait);
}
if (items.length >= options.maxSize) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
clearTimeoutIfNecessary(timeout);
timeout = null;
_kickBatchOff();
}
@ -159,10 +158,8 @@ export function createWaitBatcher<ItemType>(
`Flushing start ${options.name} for waitBatcher ` +
`items.length=${items.length}`
);
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
clearTimeoutIfNecessary(timeout);
timeout = null;
while (anyPending()) {
// eslint-disable-next-line no-await-in-loop

View File

@ -1,6 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
export function waitForOnline(
navigator: Readonly<{ onLine: boolean }>,
onlineEventTarget: EventTarget,
@ -23,9 +25,7 @@ export function waitForOnline(
const cleanup = () => {
onlineEventTarget.removeEventListener('online', listener);
if (typeof timeoutId === 'number') {
clearTimeout(timeoutId);
}
clearTimeoutIfNecessary(timeoutId);
};
onlineEventTarget.addEventListener('online', listener);