Skip to content

Commit 9cbca57

Browse files
Skip outdated workspaces in calendar integration (#9561)
1 parent 8f2726a commit 9cbca57

File tree

2 files changed

+96
-7
lines changed

2 files changed

+96
-7
lines changed

services/calendar/pod-calendar/src/calendarController.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import { getIntegrations } from './integrations'
2727
import { WorkspaceClient } from './workspaceClient'
2828
import { cleanUserByEmail } from './kvsUtils'
2929

30+
interface WorkspaceStateInfo {
31+
shouldStart: boolean
32+
needRecheck: boolean
33+
}
34+
3035
export class CalendarController {
3136
protected static _instance: CalendarController
3237

@@ -72,10 +77,13 @@ export class CalendarController {
7277
if (ids.length === 0) return
7378
const limiter = new RateLimiter(config.InitLimit)
7479
const infos = await this.accountClient.getWorkspacesInfo(ids)
80+
const outdatedWorkspaces = new Set<WorkspaceUuid>()
7581
for (let index = 0; index < infos.length; index++) {
7682
const info = infos[index]
7783
const integrations = groups.get(info.uuid) ?? []
78-
if (await this.checkWorkspace(info, integrations)) {
84+
const { shouldStart, needRecheck } = await this.checkWorkspace(info, integrations)
85+
86+
if (shouldStart) {
7987
await limiter.add(async () => {
8088
try {
8189
this.ctx.info('start workspace', { workspace: info.uuid })
@@ -85,27 +93,105 @@ export class CalendarController {
8593
}
8694
})
8795
}
96+
97+
if (needRecheck) {
98+
outdatedWorkspaces.add(info.uuid)
99+
}
100+
88101
if (index % 10 === 0) {
89102
this.ctx.info('starting progress', { value: index + 1, total: infos.length })
90103
}
91104
}
92105
await limiter.waitProcessing()
93106
this.ctx.info('Started all workspaces', { count: infos.length })
107+
108+
if (outdatedWorkspaces.size > 0) {
109+
this.ctx.info('Found outdated workspaces for future recheck', { count: outdatedWorkspaces.size })
110+
// Schedule recheck for outdated workspaces
111+
const outdatedGroups = new Map<WorkspaceUuid, Integration[]>()
112+
for (const workspaceId of outdatedWorkspaces) {
113+
const integrations = groups.get(workspaceId)
114+
if (integrations !== undefined) {
115+
outdatedGroups.set(workspaceId, integrations)
116+
}
117+
}
118+
void this.recheckOutdatedWorkspaces(outdatedGroups)
119+
}
94120
}
95121

96-
private async checkWorkspace (info: WorkspaceInfoWithStatus, integrations: Integration[]): Promise<boolean> {
122+
private async checkWorkspace (
123+
info: WorkspaceInfoWithStatus,
124+
integrations: Integration[]
125+
): Promise<WorkspaceStateInfo> {
97126
if (isDeletingMode(info.mode)) {
98127
if (integrations !== undefined) {
99128
for (const int of integrations) {
100129
await this.accountClient.deleteIntegration(int)
101130
}
102131
}
103-
return false
132+
return { shouldStart: false, needRecheck: false }
104133
}
105134
if (!isActiveMode(info.mode)) {
106135
this.ctx.info('workspace is not active', { workspaceUuid: info.uuid })
107-
return false
136+
return { shouldStart: false, needRecheck: false }
137+
}
138+
const lastVisit = (Date.now() - (info.lastVisit ?? 0)) / (3600 * 24 * 1000) // In days
139+
140+
if (lastVisit > config.WorkspaceInactivityInterval) {
141+
this.ctx.info('workspace is outdated, needs recheck', {
142+
workspaceUuid: info.uuid,
143+
lastVisitDays: lastVisit.toFixed(1)
144+
})
145+
return { shouldStart: false, needRecheck: true }
146+
}
147+
return { shouldStart: true, needRecheck: false }
148+
}
149+
150+
// TODO: Subscribe to workspace queue istead of using setTimeout
151+
async recheckOutdatedWorkspaces (outdatedGroups: Map<WorkspaceUuid, Integration[]>): Promise<void> {
152+
try {
153+
await new Promise<void>((resolve) => {
154+
setTimeout(
155+
() => {
156+
resolve()
157+
},
158+
10 * 60 * 1000
159+
) // Wait 10 minutes
160+
})
161+
162+
const ids = [...outdatedGroups.keys()]
163+
const limiter = new RateLimiter(config.InitLimit)
164+
const infos = await this.accountClient.getWorkspacesInfo(ids)
165+
const stillOutdatedGroups = new Map<WorkspaceUuid, Integration[]>()
166+
167+
for (let index = 0; index < infos.length; index++) {
168+
const info = infos[index]
169+
const integrations = outdatedGroups.get(info.uuid) ?? []
170+
const { shouldStart, needRecheck } = await this.checkWorkspace(info, integrations)
171+
172+
if (shouldStart) {
173+
await limiter.add(async () => {
174+
try {
175+
this.ctx.info('restarting previously outdated workspace', { workspace: info.uuid })
176+
await WorkspaceClient.run(this.ctx, this.accountClient, info.uuid)
177+
} catch (err) {
178+
this.ctx.error('Failed to restart workspace', { workspace: info.uuid, error: err })
179+
}
180+
})
181+
} else if (needRecheck) {
182+
// Keep this workspace for future recheck
183+
stillOutdatedGroups.set(info.uuid, integrations)
184+
}
185+
}
186+
187+
await limiter.waitProcessing()
188+
189+
if (stillOutdatedGroups.size > 0) {
190+
this.ctx.info('Still outdated workspaces, scheduling next recheck', { count: stillOutdatedGroups.size })
191+
void this.recheckOutdatedWorkspaces(stillOutdatedGroups)
192+
}
193+
} catch (err: any) {
194+
this.ctx.error('Failed to recheck outdated workspaces', { error: err })
108195
}
109-
return true
110196
}
111197
}

services/calendar/pod-calendar/src/config.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface Config {
2323
Credentials: string
2424
WATCH_URL: string
2525
InitLimit: number
26+
WorkspaceInactivityInterval: number // Interval in days to stop workspace synchronization if not visited
2627
}
2728

2829
const envMap: { [key in keyof Config]: string } = {
@@ -34,7 +35,8 @@ const envMap: { [key in keyof Config]: string } = {
3435
Credentials: 'Credentials',
3536
WATCH_URL: 'WATCH_URL',
3637
InitLimit: 'INIT_LIMIT',
37-
KvsUrl: 'KVS_URL'
38+
KvsUrl: 'KVS_URL',
39+
WorkspaceInactivityInterval: 'WORKSPACE_INACTIVITY_INTERVAL'
3840
}
3941

4042
const parseNumber = (str: string | undefined): number | undefined => (str !== undefined ? Number(str) : undefined)
@@ -48,7 +50,8 @@ const config: Config = (() => {
4850
Credentials: process.env[envMap.Credentials],
4951
InitLimit: parseNumber(process.env[envMap.InitLimit]) ?? 50,
5052
WATCH_URL: process.env[envMap.WATCH_URL],
51-
KvsUrl: process.env[envMap.KvsUrl]
53+
KvsUrl: process.env[envMap.KvsUrl],
54+
WorkspaceInactivityInterval: parseNumber(process.env[envMap.WorkspaceInactivityInterval] ?? '3') // In days
5255
}
5356

5457
const missingEnv = (Object.keys(params) as Array<keyof Config>)

0 commit comments

Comments
 (0)