mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
Scheduler abstraction (#2829)
This commit is contained in:
parent
7f3ec5acb8
commit
be1762d33f
@ -31,7 +31,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ async function getSetup() {
|
|||||||
destroy = () => {
|
destroy = () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -24,7 +24,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,4 @@ test('should enable prometheus', async () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ async function getSetup(opts?: IUnleashOptions) {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ async function getSetup() {
|
|||||||
destroy: () => {
|
destroy: () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import {
|
|||||||
import { IApiTokenStore } from '../types/stores/api-token-store';
|
import { IApiTokenStore } from '../types/stores/api-token-store';
|
||||||
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
|
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
|
||||||
import BadDataError from '../error/bad-data-error';
|
import BadDataError from '../error/bad-data-error';
|
||||||
import { minutesToMilliseconds } from 'date-fns';
|
|
||||||
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
|
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
|
||||||
import { constantTimeCompare } from '../util/constantTimeCompare';
|
import { constantTimeCompare } from '../util/constantTimeCompare';
|
||||||
import {
|
import {
|
||||||
@ -50,10 +49,6 @@ export class ApiTokenService {
|
|||||||
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
private timer: NodeJS.Timeout;
|
|
||||||
|
|
||||||
private seenTimer: NodeJS.Timeout;
|
|
||||||
|
|
||||||
private activeTokens: IApiToken[] = [];
|
private activeTokens: IApiToken[] = [];
|
||||||
|
|
||||||
private eventStore: IEventStore;
|
private eventStore: IEventStore;
|
||||||
@ -76,10 +71,6 @@ export class ApiTokenService {
|
|||||||
this.environmentStore = environmentStore;
|
this.environmentStore = environmentStore;
|
||||||
this.logger = config.getLogger('/services/api-token-service.ts');
|
this.logger = config.getLogger('/services/api-token-service.ts');
|
||||||
this.fetchActiveTokens();
|
this.fetchActiveTokens();
|
||||||
this.timer = setInterval(
|
|
||||||
() => this.fetchActiveTokens(),
|
|
||||||
minutesToMilliseconds(1),
|
|
||||||
).unref();
|
|
||||||
this.updateLastSeen();
|
this.updateLastSeen();
|
||||||
if (config.authentication.initApiTokens.length > 0) {
|
if (config.authentication.initApiTokens.length > 0) {
|
||||||
process.nextTick(async () =>
|
process.nextTick(async () =>
|
||||||
@ -103,11 +94,6 @@ export class ApiTokenService {
|
|||||||
this.lastSeenSecrets = new Set<string>();
|
this.lastSeenSecrets = new Set<string>();
|
||||||
await this.store.markSeenAt(toStore);
|
await this.store.markSeenAt(toStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.seenTimer = setTimeout(
|
|
||||||
async () => this.updateLastSeen(),
|
|
||||||
minutesToMilliseconds(3),
|
|
||||||
).unref();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllTokens(): Promise<IApiToken[]> {
|
public async getAllTokens(): Promise<IApiToken[]> {
|
||||||
@ -286,11 +272,4 @@ export class ApiTokenService {
|
|||||||
return `${projects[0]}:${environment}.${randomStr}`;
|
return `${projects[0]}:${environment}.${randomStr}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
clearTimeout(this.seenTimer);
|
|
||||||
this.timer = null;
|
|
||||||
this.seenTimer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ import { InstanceStatsService } from './instance-stats-service';
|
|||||||
import { FavoritesService } from './favorites-service';
|
import { FavoritesService } from './favorites-service';
|
||||||
import MaintenanceService from './maintenance-service';
|
import MaintenanceService from './maintenance-service';
|
||||||
import ExportImportService from './export-import-service';
|
import ExportImportService from './export-import-service';
|
||||||
|
import SchedulerService from './scheduler-service';
|
||||||
|
import { minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
export const createServices = (
|
export const createServices = (
|
||||||
stores: IUnleashStores,
|
stores: IUnleashStores,
|
||||||
@ -139,6 +141,17 @@ export const createServices = (
|
|||||||
settingService,
|
settingService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const schedulerService = new SchedulerService(config.getLogger);
|
||||||
|
schedulerService.schedule(
|
||||||
|
apiTokenService.fetchActiveTokens.bind(apiTokenService),
|
||||||
|
minutesToMilliseconds(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
schedulerService.schedule(
|
||||||
|
apiTokenService.updateLastSeen.bind(apiTokenService),
|
||||||
|
minutesToMilliseconds(3),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessService,
|
accessService,
|
||||||
addonService,
|
addonService,
|
||||||
|
95
src/lib/services/scheduler-service.test.ts
Normal file
95
src/lib/services/scheduler-service.test.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import SchedulerService from './scheduler-service';
|
||||||
|
|
||||||
|
function ms(timeMs) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, timeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLogger = () => {
|
||||||
|
const records = [];
|
||||||
|
const logger = () => ({
|
||||||
|
error(...args: any[]) {
|
||||||
|
records.push(args);
|
||||||
|
},
|
||||||
|
debug() {},
|
||||||
|
info() {},
|
||||||
|
warn() {},
|
||||||
|
fatal() {},
|
||||||
|
getRecords() {
|
||||||
|
return records;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.getRecords = () => records;
|
||||||
|
|
||||||
|
return logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
test('Can schedule a single regular job', async () => {
|
||||||
|
const schedulerService = new SchedulerService(getLogger());
|
||||||
|
const job = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
await ms(15);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(1);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can schedule multiple jobs at the same interval', async () => {
|
||||||
|
const schedulerService = new SchedulerService(getLogger());
|
||||||
|
const job = jest.fn();
|
||||||
|
const anotherJob = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
schedulerService.schedule(anotherJob, 10);
|
||||||
|
await ms(15);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(1);
|
||||||
|
expect(anotherJob).toBeCalledTimes(1);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can schedule multiple jobs at the different intervals', async () => {
|
||||||
|
const schedulerService = new SchedulerService(getLogger());
|
||||||
|
const job = jest.fn();
|
||||||
|
const anotherJob = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
schedulerService.schedule(anotherJob, 20);
|
||||||
|
await ms(25);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(2);
|
||||||
|
expect(anotherJob).toBeCalledTimes(1);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can handle crash of a async job', async () => {
|
||||||
|
const logger = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
|
const job = async () => {
|
||||||
|
await Promise.reject('async reason');
|
||||||
|
};
|
||||||
|
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
await ms(15);
|
||||||
|
|
||||||
|
schedulerService.stop();
|
||||||
|
expect(logger.getRecords()).toEqual([
|
||||||
|
['scheduled job failed', 'async reason'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can handle crash of a sync job', async () => {
|
||||||
|
const logger = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
|
const job = () => {
|
||||||
|
throw new Error('sync reason');
|
||||||
|
};
|
||||||
|
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
await ms(15);
|
||||||
|
|
||||||
|
schedulerService.stop();
|
||||||
|
expect(logger.getRecords()).toEqual([
|
||||||
|
['scheduled job failed', new Error('sync reason')],
|
||||||
|
]);
|
||||||
|
});
|
27
src/lib/services/scheduler-service.ts
Normal file
27
src/lib/services/scheduler-service.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Logger, LogProvider } from '../logger';
|
||||||
|
|
||||||
|
export default class SchedulerService {
|
||||||
|
private intervalIds: NodeJS.Timer[] = [];
|
||||||
|
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
|
constructor(getLogger: LogProvider) {
|
||||||
|
this.logger = getLogger('/services/scheduler-service.ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule(scheduledFunction: () => void, timeMs: number): void {
|
||||||
|
this.intervalIds.push(
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await scheduledFunction();
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('scheduled job failed', e);
|
||||||
|
}
|
||||||
|
}, timeMs).unref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
this.intervalIds.forEach(clearInterval);
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,6 @@ async function createApp(
|
|||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
services.clientInstanceService.destroy();
|
services.clientInstanceService.destroy();
|
||||||
services.clientMetricsServiceV2.destroy();
|
services.clientMetricsServiceV2.destroy();
|
||||||
services.apiTokenService.destroy();
|
|
||||||
services.proxyService.destroy();
|
services.proxyService.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user