@@ -27,6 +27,11 @@ import { getIntegrations } from './integrations'
27
27
import { WorkspaceClient } from './workspaceClient'
28
28
import { cleanUserByEmail } from './kvsUtils'
29
29
30
+ interface WorkspaceStateInfo {
31
+ shouldStart : boolean
32
+ needRecheck : boolean
33
+ }
34
+
30
35
export class CalendarController {
31
36
protected static _instance : CalendarController
32
37
@@ -72,10 +77,13 @@ export class CalendarController {
72
77
if ( ids . length === 0 ) return
73
78
const limiter = new RateLimiter ( config . InitLimit )
74
79
const infos = await this . accountClient . getWorkspacesInfo ( ids )
80
+ const outdatedWorkspaces = new Set < WorkspaceUuid > ( )
75
81
for ( let index = 0 ; index < infos . length ; index ++ ) {
76
82
const info = infos [ index ]
77
83
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 ) {
79
87
await limiter . add ( async ( ) => {
80
88
try {
81
89
this . ctx . info ( 'start workspace' , { workspace : info . uuid } )
@@ -85,27 +93,105 @@ export class CalendarController {
85
93
}
86
94
} )
87
95
}
96
+
97
+ if ( needRecheck ) {
98
+ outdatedWorkspaces . add ( info . uuid )
99
+ }
100
+
88
101
if ( index % 10 === 0 ) {
89
102
this . ctx . info ( 'starting progress' , { value : index + 1 , total : infos . length } )
90
103
}
91
104
}
92
105
await limiter . waitProcessing ( )
93
106
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
+ }
94
120
}
95
121
96
- private async checkWorkspace ( info : WorkspaceInfoWithStatus , integrations : Integration [ ] ) : Promise < boolean > {
122
+ private async checkWorkspace (
123
+ info : WorkspaceInfoWithStatus ,
124
+ integrations : Integration [ ]
125
+ ) : Promise < WorkspaceStateInfo > {
97
126
if ( isDeletingMode ( info . mode ) ) {
98
127
if ( integrations !== undefined ) {
99
128
for ( const int of integrations ) {
100
129
await this . accountClient . deleteIntegration ( int )
101
130
}
102
131
}
103
- return false
132
+ return { shouldStart : false , needRecheck : false }
104
133
}
105
134
if ( ! isActiveMode ( info . mode ) ) {
106
135
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 } )
108
195
}
109
- return true
110
196
}
111
197
}
0 commit comments