Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/supervisor/frontend/src/ide/supervisor-service-client.ts
2501 views
1
/**
2
* Copyright (c) 2020 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 {
8
SupervisorStatusResponse,
9
IDEStatusResponse,
10
ContentStatusResponse,
11
} from "@gitpod/supervisor-api-grpc/lib/status_pb";
12
import { WorkspaceInfoResponse } from "@gitpod/supervisor-api-grpc/lib/info_pb";
13
import { workspaceUrl } from "../shared/urls";
14
import { FrontendDashboardServiceClient } from "../shared/frontend-dashboard-service";
15
import { Timeout } from "@gitpod/gitpod-protocol/lib/util/timeout";
16
17
export class SupervisorServiceClient {
18
readonly supervisorReady = this.checkReady("supervisor");
19
readonly ideReady = this.supervisorReady.then(() => this.checkReady("ide"));
20
readonly contentReady = Promise.all([this.supervisorReady]).then(() => this.checkReady("content"));
21
readonly getWorkspaceInfoPromise = this.supervisorReady.then(() => this.getWorkspaceInfo());
22
private _supervisorWillShutdown: Promise<void> | undefined;
23
24
constructor(readonly serviceClient: FrontendDashboardServiceClient) {}
25
26
public get supervisorWillShutdown() {
27
if (!this._supervisorWillShutdown) {
28
this._supervisorWillShutdown = this.supervisorReady.then(() => this.checkWillShutdown());
29
}
30
return this._supervisorWillShutdown;
31
}
32
33
private async checkWillShutdown(delay = false): Promise<void> {
34
if (delay) {
35
await new Promise((resolve) => setTimeout(resolve, 1000));
36
}
37
try {
38
const wsSupervisorStatusUrl = workspaceUrl.with(() => {
39
return {
40
pathname: "/_supervisor/v1/status/supervisor/willShutdown/true",
41
};
42
});
43
const response = await fetch(wsSupervisorStatusUrl.toString(), { credentials: "include" });
44
let result;
45
if (response.ok) {
46
result = await response.json();
47
if ((result as SupervisorStatusResponse.AsObject).ok) {
48
return;
49
}
50
}
51
if (response.status === 502) {
52
// bad gateway, supervisor is gone
53
return;
54
}
55
if (response.status === 302 && response.headers.get("location")?.includes("/start/")) {
56
// redirect to start page, workspace is closed
57
return;
58
}
59
console.debug(
60
`failed to check whether is about to shutdown, trying again...`,
61
response.status,
62
response.statusText,
63
JSON.stringify(result, undefined, 2),
64
);
65
} catch (e) {
66
// network errors
67
console.debug(`failed to check whether is about to shutdown, trying again...`, e);
68
}
69
await this.checkWillShutdown(true);
70
}
71
72
private async checkReady(kind: "content" | "ide" | "supervisor", delay?: boolean): Promise<any> {
73
if (delay) {
74
await new Promise((resolve) => setTimeout(resolve, 1000));
75
}
76
77
let wait = "/wait/true";
78
if (kind == "supervisor") {
79
wait = "";
80
}
81
82
// track whenever a) we are done, or b) we try to connect (again)
83
const trackCheckReady = (p: { aborted?: boolean }, err?: any): void => {
84
if (!this.serviceClient.isCheckReadyRetryEnabled()) {
85
return;
86
}
87
88
const props: Record<string, string> = {
89
component: "supervisor-frontend",
90
instanceId: this.serviceClient.latestInfo?.instanceId ?? "",
91
userId: this.serviceClient.latestInfo?.loggedUserId ?? "",
92
readyKind: kind,
93
};
94
if (err) {
95
props.errorName = err.name;
96
props.errorStack = err.message ?? String(err);
97
}
98
99
props.aborted = String(!!p.aborted);
100
props.wait = wait;
101
102
this.serviceClient.trackEvent({
103
event: "supervisor_check_ready",
104
properties: props,
105
});
106
};
107
108
// setup a timeout, which is meant to re-establish the connection every 5 seconds
109
let isError = false;
110
const timeout = new Timeout(5000, () => this.serviceClient.isCheckReadyRetryEnabled());
111
try {
112
timeout.restart();
113
114
const wsSupervisorStatusUrl = workspaceUrl.with(() => {
115
return {
116
pathname: "/_supervisor/v1/status/" + kind + wait,
117
};
118
});
119
const response = await fetch(wsSupervisorStatusUrl.toString(), {
120
credentials: "include",
121
signal: timeout.signal,
122
});
123
let result;
124
if (response.ok) {
125
result = await response.json();
126
if (kind === "supervisor" && (result as SupervisorStatusResponse.AsObject).ok) {
127
return;
128
}
129
if (kind === "content" && (result as ContentStatusResponse.AsObject).available) {
130
return;
131
}
132
if (kind === "ide" && (result as IDEStatusResponse.AsObject).ok) {
133
return result;
134
}
135
}
136
console.debug(
137
`failed to check whether ${kind} is ready, trying again...`,
138
response.status,
139
response.statusText,
140
JSON.stringify(result, undefined, 2),
141
);
142
} catch (e) {
143
console.debug(`failed to check whether ${kind} is ready, trying again...`, e);
144
145
// we want to track this kind of errors, as they are on the critical path (of revealing the workspace)
146
isError = true;
147
trackCheckReady({ aborted: timeout.signal?.aborted }, e);
148
} finally {
149
if (!isError) {
150
// make sure we don't track twice in case of an error
151
trackCheckReady({ aborted: timeout.signal?.aborted });
152
}
153
timeout.clear();
154
}
155
return this.checkReady(kind, true);
156
}
157
158
private async getWorkspaceInfo(delay?: boolean): Promise<WorkspaceInfoResponse.AsObject> {
159
if (delay) {
160
await new Promise((resolve) => setTimeout(resolve, 1000));
161
}
162
try {
163
const getWorkspaceInfoUrl = workspaceUrl.with(() => {
164
return {
165
pathname: "_supervisor/v1/info/workspace",
166
};
167
});
168
const response = await fetch(getWorkspaceInfoUrl.toString(), { credentials: "include" });
169
let result;
170
if (response.ok) {
171
result = await response.json();
172
return result;
173
}
174
console.debug(
175
`failed to get workspace info, trying again...`,
176
response.status,
177
response.statusText,
178
JSON.stringify(result, undefined, 2),
179
);
180
} catch (e) {
181
console.debug(`failed to get workspace info, trying again...`, e);
182
}
183
return this.getWorkspaceInfo(true);
184
}
185
}
186
187