RoutineProfileRefresher: Track instances, only start() once, min sleep

This commit is contained in:
Scott Nonnenberg 2022-08-04 14:43:47 -07:00 committed by GitHub
parent 41081cb620
commit 276435f035
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 21 deletions

View file

@ -14,7 +14,7 @@ import type { ConversationModel } from './models/conversations';
import type { StorageInterface } from './types/Storage.d'; import type { StorageInterface } from './types/Storage.d';
import * as Errors from './types/errors'; import * as Errors from './types/errors';
import { getProfile } from './util/getProfile'; import { getProfile } from './util/getProfile';
import { MINUTE, HOUR, DAY, MONTH } from './util/durations'; import { MINUTE, HOUR, DAY, WEEK, MONTH } from './util/durations';
const STORAGE_KEY = 'lastAttemptedToRefreshProfilesAt'; const STORAGE_KEY = 'lastAttemptedToRefreshProfilesAt';
const MAX_AGE_TO_BE_CONSIDERED_ACTIVE = MONTH; const MAX_AGE_TO_BE_CONSIDERED_ACTIVE = MONTH;
@ -23,8 +23,11 @@ const MAX_CONVERSATIONS_TO_REFRESH = 50;
const MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN = 12 * HOUR; const MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN = 12 * HOUR;
const MIN_REFRESH_DELAY = MINUTE; const MIN_REFRESH_DELAY = MINUTE;
let idCounter = 1;
export class RoutineProfileRefresher { export class RoutineProfileRefresher {
private interval: NodeJS.Timeout | undefined; private started = false;
private id: number;
constructor( constructor(
private readonly options: { private readonly options: {
@ -32,12 +35,24 @@ export class RoutineProfileRefresher {
getOurConversationId: () => string | undefined; getOurConversationId: () => string | undefined;
storage: Pick<StorageInterface, 'get' | 'put'>; storage: Pick<StorageInterface, 'get' | 'put'>;
} }
) {} ) {
// We keep track of how many of these classes we create, because we suspect that
// there might be too many...
idCounter += 1;
this.id = idCounter;
log.info(
`Creating new RoutineProfileRefresher instance with id ${this.id}`
);
}
public async start(): Promise<void> { public async start(): Promise<void> {
if (this.interval !== undefined) { const logId = `RoutineProfileRefresher.start/${this.id}`;
clearInterval(this.interval);
if (this.started) {
log.warn(`${logId}: already started!`);
return;
} }
this.started = true;
const { storage, getAllConversations, getOurConversationId } = this.options; const { storage, getAllConversations, getOurConversationId } = this.options;
@ -45,14 +60,14 @@ export class RoutineProfileRefresher {
while (true) { while (true) {
const refreshInMs = timeUntilNextRefresh(storage); const refreshInMs = timeUntilNextRefresh(storage);
log.info(`routineProfileRefresh: waiting for ${refreshInMs}ms`); log.info(`${logId}: waiting for ${refreshInMs}ms`);
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await sleep(refreshInMs); await sleep(refreshInMs);
const ourConversationId = getOurConversationId(); const ourConversationId = getOurConversationId();
if (!ourConversationId) { if (!ourConversationId) {
log.warn('routineProfileRefresh: missing our conversation id'); log.warn(`${logId}: missing our conversation id`);
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await sleep(MIN_REFRESH_DELAY); await sleep(MIN_REFRESH_DELAY);
@ -66,10 +81,11 @@ export class RoutineProfileRefresher {
allConversations: getAllConversations(), allConversations: getAllConversations(),
ourConversationId, ourConversationId,
storage, storage,
id: this.id,
}); });
} catch (error) { } catch (error) {
log.error('routineProfileRefresh: failure', Errors.toLogFormat(error)); log.error(`${logId}: failure`, Errors.toLogFormat(error));
} finally {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await sleep(MIN_REFRESH_DELAY); await sleep(MIN_REFRESH_DELAY);
} }
@ -81,24 +97,26 @@ export async function routineProfileRefresh({
allConversations, allConversations,
ourConversationId, ourConversationId,
storage, storage,
id,
// Only for tests // Only for tests
getProfileFn = getProfile, getProfileFn = getProfile,
}: { }: {
allConversations: ReadonlyArray<ConversationModel>; allConversations: ReadonlyArray<ConversationModel>;
ourConversationId: string; ourConversationId: string;
storage: Pick<StorageInterface, 'get' | 'put'>; storage: Pick<StorageInterface, 'get' | 'put'>;
id: number;
getProfileFn?: typeof getProfile; getProfileFn?: typeof getProfile;
}): Promise<void> { }): Promise<void> {
log.info('routineProfileRefresh: starting'); const logId = `routineProfileRefresh/${id}`;
log.info(`${logId}: starting`);
const refreshInMs = timeUntilNextRefresh(storage); const refreshInMs = timeUntilNextRefresh(storage);
if (refreshInMs > 0) { if (refreshInMs > 0) {
log.info('routineProfileRefresh: too soon to refresh. Doing nothing'); log.info(`${logId}: too soon to refresh. Doing nothing`);
return; return;
} }
log.info('routineProfileRefresh: updating last refresh time'); log.info(`${logId}: updating last refresh time`);
await storage.put(STORAGE_KEY, Date.now()); await storage.put(STORAGE_KEY, Date.now());
const conversationsToRefresh = getConversationsToRefresh( const conversationsToRefresh = getConversationsToRefresh(
@ -106,7 +124,7 @@ export async function routineProfileRefresh({
ourConversationId ourConversationId
); );
log.info('routineProfileRefresh: starting to refresh conversations'); log.info(`${logId}: starting to refresh conversations`);
let totalCount = 0; let totalCount = 0;
let successCount = 0; let successCount = 0;
@ -114,20 +132,18 @@ export async function routineProfileRefresh({
async function refreshConversation( async function refreshConversation(
conversation: ConversationModel conversation: ConversationModel
): Promise<void> { ): Promise<void> {
log.info( log.info(`${logId}: refreshing profile for ${conversation.idForLogging()}`);
`routineProfileRefresh: refreshing profile for ${conversation.idForLogging()}`
);
totalCount += 1; totalCount += 1;
try { try {
await getProfileFn(conversation.get('uuid'), conversation.get('e164')); await getProfileFn(conversation.get('uuid'), conversation.get('e164'));
log.info( log.info(
`routineProfileRefresh: refreshed profile for ${conversation.idForLogging()}` `${logId}: refreshed profile for ${conversation.idForLogging()}`
); );
successCount += 1; successCount += 1;
} catch (err) { } catch (err) {
log.error( log.error(
`routineProfileRefresh: refreshed profile for ${conversation.idForLogging()}`, `${logId}: refreshed profile for ${conversation.idForLogging()}`,
err?.stack || err err?.stack || err
); );
} }
@ -144,7 +160,7 @@ export async function routineProfileRefresh({
await refreshQueue.onIdle(); await refreshQueue.onIdle();
log.info( log.info(
`routineProfileRefresh: successfully refreshed ${successCount} out of ${totalCount} conversation(s)` `${logId}: successfully refreshed ${successCount} out of ${totalCount} conversation(s)`
); );
} }
@ -158,7 +174,7 @@ function timeUntilNextRefresh(storage: Pick<StorageInterface, 'get'>): number {
if (isNormalNumber(storedValue)) { if (isNormalNumber(storedValue)) {
const planned = storedValue + MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN; const planned = storedValue + MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN;
const now = Date.now(); const now = Date.now();
return Math.max(0, planned - now); return Math.min(Math.max(0, planned - now), WEEK);
} }
assert( assert(

View file

@ -89,6 +89,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: UUID.generate().toString(), ourConversationId: UUID.generate().toString(),
storage, storage,
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.notCalled(getProfileFn); sinon.assert.notCalled(getProfileFn);
@ -104,6 +105,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: UUID.generate().toString(), ourConversationId: UUID.generate().toString(),
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.calledWith( sinon.assert.calledWith(
@ -130,6 +132,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: UUID.generate().toString(), ourConversationId: UUID.generate().toString(),
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.calledOnce(getProfileFn); sinon.assert.calledOnce(getProfileFn);
@ -159,6 +162,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: me.id, ourConversationId: me.id,
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.calledWith(getProfileFn, notMe.get('uuid'), notMe.get('e164')); sinon.assert.calledWith(getProfileFn, notMe.get('uuid'), notMe.get('e164'));
@ -176,6 +180,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: UUID.generate().toString(), ourConversationId: UUID.generate().toString(),
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.calledOnce(getProfileFn); sinon.assert.calledOnce(getProfileFn);
@ -219,6 +224,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: UUID.generate().toString(), ourConversationId: UUID.generate().toString(),
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
sinon.assert.calledWith( sinon.assert.calledWith(
@ -288,6 +294,7 @@ describe('routineProfileRefresh', () => {
ourConversationId: me.id, ourConversationId: me.id,
storage: makeStorage(), storage: makeStorage(),
getProfileFn, getProfileFn,
id: 1,
}); });
[...activeConversations, ...inactiveGroupMembers].forEach(conversation => { [...activeConversations, ...inactiveGroupMembers].forEach(conversation => {