Skip to content

Commit 56874b6

Browse files
geroplroboquat
authored andcommitted
[server, et. al] Add repeat(cb, ms) as replacement for setInterval, and use it across meta components
1 parent b7a0264 commit 56874b6

File tree

15 files changed

+126
-115
lines changed

15 files changed

+126
-115
lines changed

components/ee/payment-endpoint/src/github/subscription-reconciler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Plans, Plan } from "@gitpod/gitpod-protocol/lib/plans";
1919
import * as Webhooks from '@octokit/webhooks';
2020
import fetch from "node-fetch";
2121
import { EntityManager } from 'typeorm';
22+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
2223

2324
@injectable()
2425
export class GithubSubscriptionReconciler {
@@ -199,8 +200,8 @@ export class GithubSubscriptionReconciler {
199200
}
200201

201202
public start() {
202-
setInterval(() => this.reconciliationTasks.enqueue(() => this.reconcilePendingEvents()), 1 * 60 * 1000); // every one minute
203-
setInterval(() => this.reconciliationTasks.enqueue(async () => {
203+
repeat(() => this.reconciliationTasks.enqueue(() => this.reconcilePendingEvents()), 1 * 60 * 1000); // every one minute
204+
repeat(() => this.reconciliationTasks.enqueue(async () => {
204205
try {
205206
// it's important we reconcile the latest pending events first before attempting to interpret GitHub's information.
206207
await this.reconcilePendingEvents();

components/gitpod-db/src/typeorm/deleted-entry-gc.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
import { injectable, inject } from "inversify";
88
import { TypeORM } from "./typeorm";
99
import { Config } from "../config";
10+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
11+
import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol";
1012

1113

1214
@injectable()
13-
export class DeletedEntryGC {
15+
export class DeletedEntryGC implements Disposable {
1416
@inject(TypeORM) protected readonly typeORM: TypeORM;
1517
@inject(Config) protected readonly config: Config;
1618

19+
protected readonly disposables = new DisposableCollection();
20+
1721
public start() {
1822
const cfg = this.config.deletedEntryGCConfig;
1923
if (!cfg.enabled) {
@@ -22,9 +26,15 @@ export class DeletedEntryGC {
2226
}
2327

2428
console.info(`Deleted Entries GC enabled (running every ${cfg.intervalMS/(60*1000)} minutes)`);
25-
setInterval(() => {
26-
this.gc().catch(e => console.error("error while removing deleted entries", e));
27-
}, cfg.intervalMS);
29+
this.disposables.push(
30+
repeat(() => {
31+
this.gc().catch(e => console.error("error while removing deleted entries", e));
32+
}, cfg.intervalMS)
33+
);
34+
}
35+
36+
public dispose() {
37+
this.disposables.dispose();
2838
}
2939

3040
protected async gc() {

components/gitpod-protocol/src/util/garbage-collected-cache.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7+
import { repeat } from "./repeat";
8+
79

810
interface CacheEntry<T> {
911
key: string;
@@ -46,7 +48,7 @@ export class GarbageCollectedCache<T> {
4648
}
4749

4850
protected regularlyCollectGarbage() {
49-
setInterval(() => this.collectGarbage(), this.gcIntervalSeconds * 1000);
51+
repeat(() => this.collectGarbage(), this.gcIntervalSeconds * 1000);
5052
}
5153

5254
protected collectGarbage() {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { Disposable } from "..";
8+
import { log } from "./logging";
9+
10+
/**
11+
* This intends to be a drop-in replacement for 'setInterval' implemented with a 'setTimeout' chain
12+
* to ensure we're not creating more timeouts than we can process.
13+
* @param op
14+
* @param everyMilliseconds
15+
* @returns
16+
*/
17+
export function repeat(op: () => Promise<void> | void, everyMilliseconds: number): Disposable {
18+
let timer: NodeJS.Timeout | undefined = undefined;
19+
let stopped = false;
20+
const repeated = () => {
21+
if (stopped) {
22+
// in case we missed the clearTimeout i 'await'
23+
return;
24+
}
25+
26+
timer = setTimeout(async () => {
27+
try {
28+
await op();
29+
} catch (err) {
30+
// catch error here to
31+
log.error(err);
32+
}
33+
34+
repeated(); // chain ourselves - after the 'await'
35+
}, everyMilliseconds);
36+
};
37+
repeated();
38+
39+
return Disposable.create(() => {
40+
stopped = true;
41+
if (timer) {
42+
clearTimeout(timer);
43+
}
44+
});
45+
}

components/gitpod-protocol/src/util/repeater.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

components/server/ee/src/prebuilds/prebuilt-status-maintainer.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import { WorkspaceDB, TracedWorkspaceDB, DBWithTracing } from '@gitpod/gitpod-db
1010
import { v4 as uuidv4 } from 'uuid';
1111
import { HeadlessWorkspaceEvent } from '@gitpod/gitpod-protocol/lib/headless-workspace-log';
1212
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
13-
import { PrebuiltWorkspaceUpdatable, PrebuiltWorkspace, Disposable } from '@gitpod/gitpod-protocol';
13+
import { PrebuiltWorkspaceUpdatable, PrebuiltWorkspace, Disposable, DisposableCollection } from '@gitpod/gitpod-protocol';
1414
import { TraceContext } from '@gitpod/gitpod-protocol/lib/util/tracing';
1515
import { LocalMessageBroker } from '../../../src/messaging/local-message-broker';
16+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1617

1718
export interface CheckRunInfo {
1819
owner: string;
@@ -33,15 +34,18 @@ export class PrebuildStatusMaintainer implements Disposable {
3334
@inject(TracedWorkspaceDB) protected readonly workspaceDB: DBWithTracing<WorkspaceDB>;
3435
@inject(LocalMessageBroker) protected readonly localMessageBroker: LocalMessageBroker;
3536
protected githubApiProvider: AuthenticatedGithubProvider;
36-
protected messagebusListener?: Disposable;
37-
protected periodicChecker?: NodeJS.Timer;
37+
protected readonly disposables = new DisposableCollection();
3838

3939
start(githubApiProvider: AuthenticatedGithubProvider): void {
4040
// set github before registering the msgbus listener - otherwise an incoming message and the github set might race
4141
this.githubApiProvider = githubApiProvider;
4242

43-
this.messagebusListener = this.localMessageBroker.listenForPrebuildUpdatableEvents((ctx, msg) => this.handlePrebuildFinished(ctx, msg));
44-
this.periodicChecker = setInterval(this.periodicUpdatableCheck.bind(this), 60 * 1000) as any as NodeJS.Timer;
43+
this.disposables.push(
44+
this.localMessageBroker.listenForPrebuildUpdatableEvents((ctx, msg) => this.handlePrebuildFinished(ctx, msg))
45+
);
46+
this.disposables.push(
47+
repeat(this.periodicUpdatableCheck.bind(this), 60 * 1000)
48+
);
4549
log.debug("prebuild updatatable status maintainer started");
4650
}
4751

@@ -207,13 +211,6 @@ export class PrebuildStatusMaintainer implements Disposable {
207211
}
208212

209213
dispose(): void {
210-
if (this.messagebusListener) {
211-
this.messagebusListener.dispose();
212-
this.messagebusListener = undefined;
213-
}
214-
if (this.periodicChecker !== undefined) {
215-
clearInterval(this.periodicChecker);
216-
this.periodicChecker = undefined;
217-
}
214+
this.disposables.dispose();
218215
}
219216
}

components/server/ee/src/workspace/snapshot-service.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SafePromise } from "@gitpod/gitpod-protocol/lib/util/safe-promise";
1212
import { StorageClient } from "../../../src/storage/storage-client";
1313
import { ConsensusLeaderQorum } from "../../../src/consensus/consensus-leader-quorum";
1414
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
15+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1516

1617
const SNAPSHOT_TIMEOUT_SECONDS = 60 * 30;
1718
const SNAPSHOT_POLL_INTERVAL_SECONDS = 5;
@@ -35,10 +36,7 @@ export class SnapshotService {
3536
protected readonly runningSnapshots: Map<string, Promise<void>> = new Map();
3637

3738
public async start(): Promise<Disposable> {
38-
const timer = setInterval(() => this.pickupAndDriveFromDbIfWeAreLeader().catch(log.error), SNAPSHOT_DB_POLL_INTERVAL_SECONDS * 1000);
39-
return {
40-
dispose: () => clearInterval(timer)
41-
}
39+
return repeat(() => this.pickupAndDriveFromDbIfWeAreLeader().catch(log.error), SNAPSHOT_DB_POLL_INTERVAL_SECONDS * 1000);
4240
}
4341

4442
public async pickupAndDriveFromDbIfWeAreLeader() {

components/server/src/consensus/consensus-leader-quorum.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ConsensusLeaderMessenger, HeartbeatMessage, RequestVoteMessage, CastVot
99
import { Disposable } from "@gitpod/gitpod-protocol";
1010
import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred";
1111
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
12+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1213

1314
@injectable()
1415
/* Implements the leader election mechanism of the Raft concensus algorithm:
@@ -19,7 +20,6 @@ import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1920
export class ConsensusLeaderQorum implements Disposable {
2021
@inject(ConsensusLeaderMessenger) protected readonly messenger: ConsensusLeaderMessenger;
2122

22-
protected clock: NodeJS.Timeout | undefined;
2323
protected messages: RaftMessage[] = [];
2424
protected readonly disposables: Disposable[] = [];
2525
protected consensusAchieved = new Deferred<boolean>();
@@ -67,8 +67,7 @@ export class ConsensusLeaderQorum implements Disposable {
6767
// register with the messenger
6868
this.uid = await this.messenger.register();
6969

70-
this.clock = setInterval(() => this.beatClock().catch((err) => log.error("consensus beatClock", err)), this.clockPeriod);
71-
this.disposables.push({ dispose: () => clearTimeout(this.clock!) });
70+
this.disposables.push(repeat(() => this.beatClock().catch((err) => log.error("consensus beatClock", err)), this.clockPeriod));
7271
this.disposables.push(this.messenger.on("heartbeat", msg => this.messages.push(msg)));
7372
this.disposables.push(this.messenger.on("requestVote", msg => this.messages.push(msg)));
7473
this.disposables.push(this.messenger.on("castVote", msg => this.messages.push(msg)));

components/server/src/express-util.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ import * as express from 'express';
1111
import * as crypto from 'crypto';
1212
import { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url';
1313
import * as session from 'express-session';
14+
import { repeat } from '@gitpod/gitpod-protocol/lib/util/repeat';
1415

1516
export const pingPong: WsRequestHandler = (ws, req, next) => {
1617
let pingSentTimer: any;
17-
const timer = setInterval(() => {
18+
const disposable = repeat(() => {
1819
if (ws.readyState !== ws.OPEN) {
1920
return;
2021
}
2122
// wait 10 secs for a pong
2223
pingSentTimer = setTimeout(() => {
2324
// Happens very often, we do not want to spam the logs here
2425
ws.terminate();
26+
disposable.dispose();
2527
}, 10000);
2628
ws.ping();
2729
}, 30000)
@@ -35,7 +37,7 @@ export const pingPong: WsRequestHandler = (ws, req, next) => {
3537
ws.pong(data);
3638
});
3739
ws.on('close', () => {
38-
clearInterval(timer);
40+
disposable.dispose();
3941
})
4042
next();
4143
}

components/server/src/ide-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Disposable, DisposableCollection, Emitter } from '@gitpod/gitpod-protoc
88
import { filePathTelepresenceAware } from '@gitpod/gitpod-protocol/lib/env';
99
import { IDEOptions } from '@gitpod/gitpod-protocol/lib/ide-protocol';
1010
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
11+
import { repeat } from '@gitpod/gitpod-protocol/lib/util/repeat';
1112
import * as Ajv from 'ajv';
1213
import * as cp from 'child_process';
1314
import * as crypto from 'crypto';
@@ -91,7 +92,7 @@ export class IDEConfigService {
9192
this.validate = this.ajv.compile(scheme);
9293
this.reconcile("initial");
9394
fs.watchFile(this.configPath, () => this.reconcile("file changed"));
94-
setInterval(() => this.reconcile("interval"), 60 * 60 * 1000 /* 1 hour */);
95+
repeat(() => this.reconcile("interval"), 60 * 60 * 1000 /* 1 hour */);
9596
}
9697

9798
get config(): Promise<IDEConfig> {

components/server/src/one-time-secret-server.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,27 @@ import { injectable, inject } from "inversify";
88
import * as express from 'express';
99
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1010
import { OneTimeSecretDB, DBWithTracing, TracedOneTimeSecretDB } from "@gitpod/gitpod-db/lib";
11-
import { Disposable } from "@gitpod/gitpod-protocol";
11+
import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol";
1212
import * as opentracing from 'opentracing';
1313
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1414
import { Config } from "./config";
15+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1516

1617
@injectable()
1718
export class OneTimeSecretServer implements Disposable {
1819
@inject(Config) protected readonly config: Config;
1920
@inject(TracedOneTimeSecretDB) protected readonly oneTimeSecretDB: DBWithTracing<OneTimeSecretDB>;
2021

21-
protected pruneTimeout: NodeJS.Timeout | undefined;
22+
protected readonly disposables = new DisposableCollection();
2223

2324
public startPruningExpiredSecrets() {
24-
this.pruneTimeout = setInterval(() => this.oneTimeSecretDB.trace({}).pruneExpired(), 5*60*1000);
25+
this.disposables.push(
26+
repeat(() => this.oneTimeSecretDB.trace({}).pruneExpired(), 5*60*1000)
27+
);
2528
}
2629

2730
dispose(): void {
28-
if (!this.pruneTimeout) {
29-
return;
30-
}
31-
32-
clearInterval(this.pruneTimeout);
33-
this.pruneTimeout = undefined;
31+
this.disposables.dispose();
3432
}
3533

3634
get apiRouter(): express.Router {

components/server/src/user/token-garbage-collector.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Disposable } from "@gitpod/gitpod-protocol";
1111
import { ConsensusLeaderQorum } from "../consensus/consensus-leader-quorum";
1212
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1313
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
14+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1415

1516
@injectable()
1617
export class TokenGarbageCollector {
@@ -23,7 +24,7 @@ export class TokenGarbageCollector {
2324

2425
public async start(intervalSeconds?: number): Promise<Disposable> {
2526
const intervalSecs = (intervalSeconds || TokenGarbageCollector.GC_CYCLE_INTERVAL_SECONDS);
26-
const timer = setInterval(async () => {
27+
return repeat(async () => {
2728
try {
2829
if (await this.leaderQuorum.areWeLeader()) {
2930
await this.collectExpiredTokenEntries();
@@ -32,9 +33,6 @@ export class TokenGarbageCollector {
3233
log.error("token garbage collector", err);
3334
}
3435
}, intervalSecs * 1000);
35-
return {
36-
dispose: () => clearInterval(timer)
37-
}
3836
}
3937

4038
protected async collectExpiredTokenEntries() {

components/server/src/workspace/garbage-collector.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as opentracing from 'opentracing';
1414
import { TracedWorkspaceDB, DBWithTracing, WorkspaceDB } from "@gitpod/gitpod-db/lib";
1515
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1616
import { Config } from "../config";
17+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
1718

1819
/**
1920
* The WorkspaceGarbageCollector has two tasks:
@@ -34,10 +35,7 @@ export class WorkspaceGarbageCollector {
3435
dispose: () => {}
3536
}
3637
}
37-
const timer = setInterval(async () => this.garbageCollectWorkspacesIfLeader(), 30 * 60 * 1000);
38-
return {
39-
dispose: () => clearInterval(timer)
40-
}
38+
return repeat(async () => this.garbageCollectWorkspacesIfLeader(), 30 * 60 * 1000);
4139
}
4240

4341
public async garbageCollectWorkspacesIfLeader() {

0 commit comments

Comments
 (0)