Skip to content

Commit a013275

Browse files
committed
Recreate connect client if we get New streams cannot be created after receiving a GOAWAY error
Revert this once it's fixed upstream
1 parent 14d3bc1 commit a013275

File tree

1 file changed

+57
-23
lines changed

1 file changed

+57
-23
lines changed

src/publicApi.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,23 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
5252

5353
private workspaceStatusStreamMap = new Map<string, { onStatusChanged: vscode.Event<WorkspaceStatus>; dispose: (force?: boolean) => void; increment: () => void }>();
5454

55-
constructor(accessToken: string, gitpodHost: string, private logger: ILogService) {
55+
constructor(private accessToken: string, private gitpodHost: string, private logger: ILogService) {
5656
super();
5757

58-
const serviceUrl = new URL(gitpodHost);
58+
this.createClients();
59+
60+
this.metricsReporter = new MetricsReporter(gitpodHost, logger);
61+
if (isTelemetryEnabled()) {
62+
this.metricsReporter.startReporting();
63+
}
64+
}
65+
66+
private createClients() {
67+
const serviceUrl = new URL(this.gitpodHost);
5968
serviceUrl.hostname = `api.${serviceUrl.hostname}`;
6069

6170
const authInterceptor: Interceptor = (next) => async (req) => {
62-
req.header.set('Authorization', `Bearer ${accessToken}`);
71+
req.header.set('Authorization', `Bearer ${this.accessToken}`);
6372
return await next(req);
6473
};
6574
const metricsInterceptor = getConnectMetricsInterceptor();
@@ -75,57 +84,54 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
7584
this.workspaceService = createPromiseClient(WorkspacesService, transport);
7685
this.userService = createPromiseClient(UserService, transport);
7786
this.ideClientService = createPromiseClient(IDEClientService, transport);
78-
79-
this.metricsReporter = new MetricsReporter(gitpodHost, logger);
80-
if (isTelemetryEnabled()) {
81-
this.metricsReporter.startReporting();
82-
}
8387
}
88+
89+
8490
async getWorkspace(workspaceId: string): Promise<Workspace> {
85-
return this._wrapError(async () => {
91+
return this._wrapError(this._workaroundGoAwayBug(async () => {
8692
const response = await this.workspaceService.getWorkspace({ workspaceId });
8793
return response.result!;
88-
});
94+
}));
8995
}
9096

9197
async startWorkspace(workspaceId: string): Promise<Workspace> {
92-
return this._wrapError(async () => {
98+
return this._wrapError(this._workaroundGoAwayBug(async () => {
9399
const response = await this.workspaceService.startWorkspace({ workspaceId });
94100
return response.result!;
95-
});
101+
}));
96102
}
97103

98104
async getOwnerToken(workspaceId: string): Promise<string> {
99-
return this._wrapError(async () => {
105+
return this._wrapError(this._workaroundGoAwayBug(async () => {
100106
const response = await this.workspaceService.getOwnerToken({ workspaceId });
101107
return response.token;
102-
});
108+
}));
103109
}
104110

105111
async getSSHKeys(): Promise<SSHKey[]> {
106-
return this._wrapError(async () => {
112+
return this._wrapError(this._workaroundGoAwayBug(async () => {
107113
const response = await this.userService.listSSHKeys({});
108114
return response.keys;
109-
});
115+
}));
110116
}
111117

112118
async sendHeartbeat(workspaceId: string): Promise<void> {
113-
return this._wrapError(async () => {
119+
return this._wrapError(this._workaroundGoAwayBug(async () => {
114120
await this.ideClientService.sendHeartbeat({ workspaceId });
115-
});
121+
}));
116122
}
117123

118124
async sendDidClose(workspaceId: string): Promise<void> {
119-
return this._wrapError(async () => {
125+
return this._wrapError(this._workaroundGoAwayBug(async () => {
120126
await this.ideClientService.sendDidClose({ workspaceId });
121-
});
127+
}));
122128
}
123129

124130
async getAuthenticatedUser(): Promise<User | undefined> {
125-
return this._wrapError(async () => {
131+
return this._wrapError(this._workaroundGoAwayBug(async () => {
126132
const response = await this.userService.getAuthenticatedUser({});
127133
return response.user;
128-
});
134+
}));
129135
}
130136

131137
workspaceStatusStreaming(workspaceId: string) {
@@ -141,7 +147,7 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
141147
if (isDisposed) { return; }
142148

143149
try {
144-
const resp = await this.workspaceService.getWorkspace({ workspaceId });
150+
const resp = await this._workaroundGoAwayBug(() => this.workspaceService.getWorkspace({ workspaceId }))();
145151
if (isDisposed) { return; }
146152
emitter.fire(resp.result!.status!);
147153
} catch (err) {
@@ -190,6 +196,14 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
190196
return;
191197
}
192198
this.logger.error(`Error in streamWorkspaceStatus for ${workspaceId}`, e);
199+
200+
// Workaround https://github.com/bufbuild/connect-es/issues/680
201+
// Remove this once it's fixed upstream
202+
const message: string = e.stack || e.message || `${e}`;
203+
if (message.includes('New streams cannot be created after receiving a GOAWAY')) {
204+
this.logger.error('Got GOAWAY bug, recreating connect client');
205+
this.createClients();
206+
}
193207
}
194208
}
195209

@@ -205,6 +219,26 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
205219
}
206220
}
207221

222+
private _workaroundGoAwayBug<T>(callback: () => Promise<T>): () => Promise<T> {
223+
return async () => {
224+
try {
225+
return await callback();
226+
} catch (e) {
227+
// Workaround https://github.com/bufbuild/connect-es/issues/680
228+
// Remove this once it's fixed upstream
229+
const message: string = e.stack || e.message || `${e}`;
230+
if (message.includes('New streams cannot be created after receiving a GOAWAY')) {
231+
this.logger.error('Got GOAWAY bug, recreating connect client');
232+
this.createClients();
233+
234+
return await callback();
235+
} else {
236+
throw e;
237+
}
238+
}
239+
};
240+
}
241+
208242
public override dispose() {
209243
super.dispose();
210244
for (const { dispose } of this.workspaceStatusStreamMap.values()) {

0 commit comments

Comments
 (0)