@@ -52,14 +52,23 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
52
52
53
53
private workspaceStatusStreamMap = new Map < string , { onStatusChanged : vscode . Event < WorkspaceStatus > ; dispose : ( force ?: boolean ) => void ; increment : ( ) => void } > ( ) ;
54
54
55
- constructor ( accessToken : string , gitpodHost : string , private logger : ILogService ) {
55
+ constructor ( private accessToken : string , private gitpodHost : string , private logger : ILogService ) {
56
56
super ( ) ;
57
57
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 ) ;
59
68
serviceUrl . hostname = `api.${ serviceUrl . hostname } ` ;
60
69
61
70
const authInterceptor : Interceptor = ( next ) => async ( req ) => {
62
- req . header . set ( 'Authorization' , `Bearer ${ accessToken } ` ) ;
71
+ req . header . set ( 'Authorization' , `Bearer ${ this . accessToken } ` ) ;
63
72
return await next ( req ) ;
64
73
} ;
65
74
const metricsInterceptor = getConnectMetricsInterceptor ( ) ;
@@ -75,57 +84,54 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
75
84
this . workspaceService = createPromiseClient ( WorkspacesService , transport ) ;
76
85
this . userService = createPromiseClient ( UserService , transport ) ;
77
86
this . ideClientService = createPromiseClient ( IDEClientService , transport ) ;
78
-
79
- this . metricsReporter = new MetricsReporter ( gitpodHost , logger ) ;
80
- if ( isTelemetryEnabled ( ) ) {
81
- this . metricsReporter . startReporting ( ) ;
82
- }
83
87
}
88
+
89
+
84
90
async getWorkspace ( workspaceId : string ) : Promise < Workspace > {
85
- return this . _wrapError ( async ( ) => {
91
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
86
92
const response = await this . workspaceService . getWorkspace ( { workspaceId } ) ;
87
93
return response . result ! ;
88
- } ) ;
94
+ } ) ) ;
89
95
}
90
96
91
97
async startWorkspace ( workspaceId : string ) : Promise < Workspace > {
92
- return this . _wrapError ( async ( ) => {
98
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
93
99
const response = await this . workspaceService . startWorkspace ( { workspaceId } ) ;
94
100
return response . result ! ;
95
- } ) ;
101
+ } ) ) ;
96
102
}
97
103
98
104
async getOwnerToken ( workspaceId : string ) : Promise < string > {
99
- return this . _wrapError ( async ( ) => {
105
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
100
106
const response = await this . workspaceService . getOwnerToken ( { workspaceId } ) ;
101
107
return response . token ;
102
- } ) ;
108
+ } ) ) ;
103
109
}
104
110
105
111
async getSSHKeys ( ) : Promise < SSHKey [ ] > {
106
- return this . _wrapError ( async ( ) => {
112
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
107
113
const response = await this . userService . listSSHKeys ( { } ) ;
108
114
return response . keys ;
109
- } ) ;
115
+ } ) ) ;
110
116
}
111
117
112
118
async sendHeartbeat ( workspaceId : string ) : Promise < void > {
113
- return this . _wrapError ( async ( ) => {
119
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
114
120
await this . ideClientService . sendHeartbeat ( { workspaceId } ) ;
115
- } ) ;
121
+ } ) ) ;
116
122
}
117
123
118
124
async sendDidClose ( workspaceId : string ) : Promise < void > {
119
- return this . _wrapError ( async ( ) => {
125
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
120
126
await this . ideClientService . sendDidClose ( { workspaceId } ) ;
121
- } ) ;
127
+ } ) ) ;
122
128
}
123
129
124
130
async getAuthenticatedUser ( ) : Promise < User | undefined > {
125
- return this . _wrapError ( async ( ) => {
131
+ return this . _wrapError ( this . _workaroundGoAwayBug ( async ( ) => {
126
132
const response = await this . userService . getAuthenticatedUser ( { } ) ;
127
133
return response . user ;
128
- } ) ;
134
+ } ) ) ;
129
135
}
130
136
131
137
workspaceStatusStreaming ( workspaceId : string ) {
@@ -141,7 +147,7 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
141
147
if ( isDisposed ) { return ; }
142
148
143
149
try {
144
- const resp = await this . workspaceService . getWorkspace ( { workspaceId } ) ;
150
+ const resp = await this . _workaroundGoAwayBug ( ( ) => this . workspaceService . getWorkspace ( { workspaceId } ) ) ( ) ;
145
151
if ( isDisposed ) { return ; }
146
152
emitter . fire ( resp . result ! . status ! ) ;
147
153
} catch ( err ) {
@@ -190,6 +196,14 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
190
196
return ;
191
197
}
192
198
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
+ }
193
207
}
194
208
}
195
209
@@ -205,6 +219,26 @@ export class GitpodPublicApi extends Disposable implements IGitpodAPI {
205
219
}
206
220
}
207
221
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
+
208
242
public override dispose ( ) {
209
243
super . dispose ( ) ;
210
244
for ( const { dispose } of this . workspaceStatusStreamMap . values ( ) ) {
0 commit comments