Skip to content

Commit

Permalink
[workspace]: add force-stop check on stopping workspaces
Browse files Browse the repository at this point in the history
Since #4910 stopped counting "stopping" workspaces for billing purposes,
any workspace caught in a "stopping" phase would never be force-stopped.
This adds a conditional "includeStopping" boolean (defaulting to `false`)
to the DB implementation and the meta-instance-controller simply includes
that phase in the search.

It was discovered that ~200 workspaces were caught in this phase (90%
prebuilds) so this phase is necessary to force-stop.
  • Loading branch information
mrsimonemms authored and roboquat committed Aug 13, 2021
1 parent df20a0a commit f35e762
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 5 deletions.
1 change: 1 addition & 0 deletions chart/templates/ws-manager-bridge-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ data:
"timeouts": {
"metaInstanceCheckIntervalSeconds": 60,
"preparingPhaseSeconds": 7200,
"stoppingPhaseSeconds": 7200,
"unknownPhaseSeconds": 600
},
"staticBridges": {{ index (include "ws-manager-list" (dict "root" . "gp" $.Values "comp" .Values.components.server) | fromYaml) "manager" | default list | toJson }}
Expand Down
8 changes: 6 additions & 2 deletions components/gitpod-db/src/typeorm/workspace-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,13 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
).map(wsinfo => wsinfo.latestInstance);
}

public async findRunningInstancesWithWorkspaces(installation?: string, userId?: string): Promise<RunningWorkspaceInfo[]> {
public async findRunningInstancesWithWorkspaces(installation?: string, userId?: string, includeStopping: boolean = false): Promise<RunningWorkspaceInfo[]> {
const params: any = {};
const conditions = ["wsi.phasePersisted != 'stopped'", "wsi.phasePersisted != 'stopping'", "wsi.deleted != TRUE"];
const conditions = ["wsi.phasePersisted != 'stopped'", "wsi.deleted != TRUE"];
if (!includeStopping) {
// This excludes instances in a 'stopping' phase
conditions.push("wsi.phasePersisted != 'stopping'");
}
if (installation) {
params.region = installation;
conditions.push("wsi.region = :region");
Expand Down
2 changes: 1 addition & 1 deletion components/gitpod-db/src/workspace-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface WorkspaceDB {
findAllWorkspaceInstances(offset: number, limit: number, orderBy: keyof WorkspaceInstance, orderDir: "ASC" | "DESC", ownerId?: string, minCreationTime?: Date, maxCreationTime?: Date, onlyRunning?: boolean, type?: WorkspaceType): Promise<{ total: number, rows: WorkspaceInstance[] }>;

findRegularRunningInstances(userId?: string): Promise<WorkspaceInstance[]>;
findRunningInstancesWithWorkspaces(installation?: string, userId?: string): Promise<RunningWorkspaceInfo[]>;
findRunningInstancesWithWorkspaces(installation?: string, userId?: string, includeStopping?: boolean): Promise<RunningWorkspaceInfo[]>;

isWhitelisted(repositoryUrl : string): Promise<boolean>;
getFeaturedRepositories(): Promise<Partial<WhitelistedRepository>[]>;
Expand Down
1 change: 1 addition & 0 deletions components/ws-manager-bridge/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface Configuration {
timeouts: {
metaInstanceCheckIntervalSeconds: number;
preparingPhaseSeconds: number;
stoppingPhaseSeconds: number;
unknownPhaseSeconds: number;
}
}
10 changes: 8 additions & 2 deletions components/ws-manager-bridge/src/meta-instance-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class MetaInstanceController {
protected readonly workspaceDB: WorkspaceDB;

protected async checkAndStopWorkspaces() {
const instances = await this.workspaceDB.findRunningInstancesWithWorkspaces(this.config.installation);
const instances = await this.workspaceDB.findRunningInstancesWithWorkspaces(this.config.installation, undefined, true);

await Promise.all(instances.map(async (instance: RunningWorkspaceInfo) => {
const logContext = { instanceId: instance.latestInstance.id };
Expand All @@ -33,11 +33,17 @@ export class MetaInstanceController {

const now = Date.now();
const creationTime = new Date(instance.latestInstance.creationTime).getTime();
const stoppingTime = new Date(instance.latestInstance.stoppingTime ?? now).getTime(); // stoppingTime only set if entered stopping state
const timedOutInPreparing = now >= creationTime + (this.config.timeouts.preparingPhaseSeconds * 1000);
const timedOutInStopping = now >= stoppingTime + (this.config.timeouts.stoppingPhaseSeconds * 1000);
const timedOutInUnknown = now >= creationTime + (this.config.timeouts.unknownPhaseSeconds * 1000);

const currentPhase = instance.latestInstance.status.phase;
if ((currentPhase === 'preparing' && timedOutInPreparing) || (currentPhase === 'unknown' && timedOutInUnknown)) {
if (
(currentPhase === 'preparing' && timedOutInPreparing) ||
(currentPhase === 'stopping' && timedOutInStopping) ||
(currentPhase === 'unknown' && timedOutInUnknown)
) {
log.info(logContext, 'MetaInstanceController: Setting workspace instance to stopped', {
creationTime,
currentPhase
Expand Down

0 comments on commit f35e762

Please sign in to comment.