diff --git a/packages/backend/src/connectionManager.ts b/packages/backend/src/connectionManager.ts index 8b54fa4d..ee17543a 100644 --- a/packages/backend/src/connectionManager.ts +++ b/packages/backend/src/connectionManager.ts @@ -7,7 +7,7 @@ import { Job, Queue, ReservedJob, Worker } from "groupmq"; import { Redis } from 'ioredis'; import { compileAzureDevOpsConfig, compileBitbucketConfig, compileGenericGitHostConfig, compileGerritConfig, compileGiteaConfig, compileGithubConfig, compileGitlabConfig } from "./repoCompileUtils.js"; import { Settings } from "./types.js"; -import { groupmqLifecycleExceptionWrapper } from "./utils.js"; +import { groupmqLifecycleExceptionWrapper, setIntervalAsync } from "./utils.js"; import { syncSearchContexts } from "./ee/syncSearchContexts.js"; import { captureEvent } from "./posthog.js"; import { PromClient } from "./promClient.js"; @@ -66,7 +66,7 @@ export class ConnectionManager { public startScheduler() { logger.debug('Starting scheduler'); - this.interval = setInterval(async () => { + this.interval = setIntervalAsync(async () => { const thresholdDate = new Date(Date.now() - this.settings.resyncConnectionIntervalMs); const timeoutDate = new Date(Date.now() - JOB_TIMEOUT_MS); diff --git a/packages/backend/src/ee/accountPermissionSyncer.ts b/packages/backend/src/ee/accountPermissionSyncer.ts index eb32ba3b..81a1a135 100644 --- a/packages/backend/src/ee/accountPermissionSyncer.ts +++ b/packages/backend/src/ee/accountPermissionSyncer.ts @@ -7,6 +7,7 @@ import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js"; import { createOctokitFromToken, getReposForAuthenticatedUser } from "../github.js"; import { createGitLabFromOAuthToken, getProjectsForAuthenticatedUser } from "../gitlab.js"; import { Settings } from "../types.js"; +import { setIntervalAsync } from "../utils.js"; const LOG_TAG = 'user-permission-syncer'; const logger = createLogger(LOG_TAG); @@ -46,7 +47,7 @@ export class AccountPermissionSyncer { logger.debug('Starting scheduler'); - this.interval = setInterval(async () => { + this.interval = setIntervalAsync(async () => { const thresholdDate = new Date(Date.now() - this.settings.experiment_userDrivenPermissionSyncIntervalMs); const accounts = await this.db.account.findMany({ diff --git a/packages/backend/src/ee/repoPermissionSyncer.ts b/packages/backend/src/ee/repoPermissionSyncer.ts index 078055ff..d48f510e 100644 --- a/packages/backend/src/ee/repoPermissionSyncer.ts +++ b/packages/backend/src/ee/repoPermissionSyncer.ts @@ -8,7 +8,7 @@ import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js"; import { createOctokitFromToken, getRepoCollaborators, GITHUB_CLOUD_HOSTNAME } from "../github.js"; import { createGitLabFromPersonalAccessToken, getProjectMembers } from "../gitlab.js"; import { Settings } from "../types.js"; -import { getAuthCredentialsForRepo } from "../utils.js"; +import { getAuthCredentialsForRepo, setIntervalAsync } from "../utils.js"; type RepoPermissionSyncJob = { jobId: string; @@ -48,7 +48,7 @@ export class RepoPermissionSyncer { logger.debug('Starting scheduler'); - this.interval = setInterval(async () => { + this.interval = setIntervalAsync(async () => { // @todo: make this configurable const thresholdDate = new Date(Date.now() - this.settings.experiment_repoDrivenPermissionSyncIntervalMs); diff --git a/packages/backend/src/repoIndexManager.ts b/packages/backend/src/repoIndexManager.ts index cccc1b90..6be40d70 100644 --- a/packages/backend/src/repoIndexManager.ts +++ b/packages/backend/src/repoIndexManager.ts @@ -12,7 +12,7 @@ import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, import { captureEvent } from './posthog.js'; import { PromClient } from './promClient.js'; import { RepoWithConnections, Settings } from "./types.js"; -import { getAuthCredentialsForRepo, getRepoPath, getShardPrefix, groupmqLifecycleExceptionWrapper, measure } from './utils.js'; +import { getAuthCredentialsForRepo, getRepoPath, getShardPrefix, groupmqLifecycleExceptionWrapper, measure, setIntervalAsync } from './utils.js'; import { indexGitRepository } from './zoekt.js'; const LOG_TAG = 'repo-index-manager'; @@ -72,9 +72,9 @@ export class RepoIndexManager { this.worker.on('error', this.onWorkerError.bind(this)); } - public async startScheduler() { + public startScheduler() { logger.debug('Starting scheduler'); - this.interval = setInterval(async () => { + this.interval = setIntervalAsync(async () => { await this.scheduleIndexJobs(); await this.scheduleCleanupJobs(); }, this.settings.reindexRepoPollingIntervalMs); diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index e85ce766..f41f32f3 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -268,3 +268,24 @@ export const groupmqLifecycleExceptionWrapper = async (name: string, logger: Log } } + +// setInterval wrapper that ensures async callbacks are not executed concurrently. +// @see: https://mottaquikarim.github.io/dev/posts/setinterval-that-blocks-on-await/ +export const setIntervalAsync = (target: () => Promise, pollingIntervalMs: number): NodeJS.Timeout => { + const setIntervalWithPromise = Promise>( + target: T + ): (...args: Parameters) => Promise => { + return async function (...args: Parameters): Promise { + if ((target as any).isRunning) return; + + (target as any).isRunning = true; + await target(...args); + (target as any).isRunning = false; + }; + } + + return setInterval( + setIntervalWithPromise(target), + pollingIntervalMs + ); +} \ No newline at end of file