Skip to content

Commit 0e36011

Browse files
committed
💄
1 parent 2e08cc7 commit 0e36011

File tree

4 files changed

+53
-40
lines changed

4 files changed

+53
-40
lines changed

src/common/ports.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Gitpod. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as net from 'net';
7+
8+
export async function isPortUsed(port: number): Promise<boolean> {
9+
return new Promise<boolean>((resolve, _reject) => {
10+
const server = net.createServer();
11+
server.once('error', () => {
12+
resolve(true);
13+
});
14+
server.once('listening', () => {
15+
server.close();
16+
resolve(false);
17+
});
18+
server.listen(port);
19+
});
20+
}

src/local-ssh/ipc/extensionServiceServer.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { NodeHttpTransport } from '@improbable-eng/grpc-web-node-http-transport'
2424
import { CreateSSHKeyPairRequest } from '@gitpod/supervisor-api-grpcweb/lib/control_pb';
2525
import * as ssh2 from 'ssh2';
2626
import { ParsedKey } from 'ssh2-streams';
27-
import { createServer as netCreateServer } from 'net';
27+
import { isPortUsed } from '../../common/ports';
2828

2929
const phaseMap: Record<WorkspaceInstanceStatus_Phase, WorkspaceInstancePhase | undefined> = {
3030
[WorkspaceInstanceStatus_Phase.CREATING]: 'pending',
@@ -204,20 +204,6 @@ export class ExtensionServiceServer extends Disposable {
204204
}
205205
}
206206

207-
async function isPortUsed(port: number): Promise<boolean> {
208-
return new Promise<boolean>((resolve, _reject) => {
209-
const server = netCreateServer();
210-
server.once('error', () => {
211-
resolve(true);
212-
});
213-
server.once('listening', () => {
214-
server.close();
215-
resolve(false);
216-
});
217-
server.listen(port);
218-
});
219-
}
220-
221207
export async function canExtensionServiceServerWork(): Promise<true> {
222208
const port = Configuration.getLocalSshExtensionIpcPort();
223209
if (!(await isPortUsed(port))) {
@@ -226,4 +212,4 @@ export async function canExtensionServiceServerWork(): Promise<true> {
226212
const extensionIpc = createClient(ExtensionServiceDefinition, createChannel(`127.0.0.1:${port}`));
227213
await extensionIpc.ping({});
228214
return true;
229-
}
215+
}

src/remoteConnector.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -689,15 +689,13 @@ export class RemoteConnector extends Disposable {
689689

690690
let useLocalSSH = await this.experiments.getUseLocalSSHProxy();
691691
if (useLocalSSH) {
692+
// If needed, revert local-app changes first
693+
await this.updateRemoteSSHConfig(true, undefined);
694+
692695
this.localSSHService.flow = sshFlow;
693696
const [_, isExtensionServerReady] = await Promise.all([
694-
(async () => {
695-
// we need to update the remote ssh config first, since another call is too late for local-ssh
696-
await this.updateRemoteSSHConfig(true, undefined);
697-
this.localSSHService.prepareInitialize();
698-
await this.localSSHService.initialized;
699-
})(),
700-
this.localSSHService.extensionServerReady(),
697+
this.localSSHService.initialize(),
698+
this.localSSHService.extensionServerReady()
701699
]);
702700
if (!isExtensionServerReady) {
703701
this.logService.error('Extension IPC server is not ready');

src/services/localSSHService.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { canExtensionServiceServerWork } from '../local-ssh/ipc/extensionService
2121
export interface ILocalSSHService {
2222
flow?: UserFlowTelemetryProperties;
2323
isSupportLocalSSH: boolean;
24-
initialized: Promise<void>;
25-
prepareInitialize: () => void;
24+
25+
initialize: () => Promise<void>;
2626
extensionServerReady: () => Promise<boolean>;
2727
}
2828

@@ -32,9 +32,11 @@ type FailedToInitializeCode = 'Unknown' | 'LockFailed' | string;
3232
const IgnoredFailedCodes: FailedToInitializeCode[] = ['ENOSPC'];
3333

3434
export class LocalSSHService extends Disposable implements ILocalSSHService {
35+
private initPromise!: Promise<void>;
36+
3537
public isSupportLocalSSH: boolean = false;
36-
public initialized!: Promise<void>;
3738
public flow?: UserFlowTelemetryProperties;
39+
3840
constructor(
3941
private readonly context: vscode.ExtensionContext,
4042
private readonly hostService: IHostService,
@@ -45,8 +47,12 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
4547
super();
4648
}
4749

48-
prepareInitialize() {
49-
this.initialized = this.initialize();
50+
async initialize(): Promise<void> {
51+
if (this.initPromise) {
52+
return this.initPromise;
53+
}
54+
55+
this.initPromise = this.doInitialize();
5056
this._register(vscode.workspace.onDidChangeConfiguration(async e => {
5157
if (
5258
e.affectsConfiguration('gitpod.lsshExtensionIpcPort') ||
@@ -59,13 +65,15 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
5965
// event, so ignore it if more settings are affected at the same time.
6066
return;
6167
}
62-
if (this.initialized) {
63-
await this.initialized;
64-
}
65-
this.initialized = this.initialize();
68+
69+
await this.initPromise;
70+
this.initPromise = this.doInitialize();
6671
}
6772
}));
73+
74+
return this.initPromise;
6875
}
76+
6977
async extensionServerReady(): Promise<boolean> {
7078
try {
7179
await canExtensionServiceServerWork();
@@ -91,34 +99,35 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
9199
}
92100
}
93101

94-
private async initialize() {
102+
private async doInitialize() {
95103
let failureCode: FailedToInitializeCode | undefined;
96-
const useLocalAPP = String(Configuration.getUseLocalApp());
97-
const lockFolder = vscode.Uri.joinPath(this.context.globalStorageUri, 'initialize.lock');
98104
try {
105+
const lockFolder = vscode.Uri.joinPath(this.context.globalStorageUri, 'initialize');
99106
await this.lock(lockFolder.fsPath, async () => {
100107
const locations = await this.copyProxyScript();
101108
await this.configureSettings(locations);
102109
this.isSupportLocalSSH = true;
103110
});
104111
} catch (e) {
105-
this.logService.error(e, 'failed to initialize');
112+
this.logService.error('Failed to initialize ssh proxy config', e);
113+
106114
let sendErrorReport = true;
107115
failureCode = 'Unknown';
108116
if (e?.code) {
109117
failureCode = e.code;
110-
sendErrorReport = !IgnoredFailedCodes.includes(e.code)
118+
sendErrorReport = !IgnoredFailedCodes.includes(e.code);
111119
}
112120
if (e?.message) {
113121
e.message = `Failed to initialize: ${e.message}`;
114122
}
115123
if (sendErrorReport) {
116-
this.telemetryService.sendTelemetryException(e, { gitpodHost: this.hostService.gitpodHost, useLocalAPP });
124+
this.telemetryService.sendTelemetryException(e, { gitpodHost: this.hostService.gitpodHost, useLocalAPP: String(Configuration.getUseLocalApp()) });
117125
}
118126
this.isSupportLocalSSH = false;
119127
}
120-
const flowData = this.flow ? this.flow : { gitpodHost: this.hostService.gitpodHost, userId: this.sessionService.safeGetUserId() };
121-
this.telemetryService.sendUserFlowStatus(this.isSupportLocalSSH ? 'success' : 'failure', { ...flowData, flow: 'local_ssh_config', failureCode, useLocalAPP });
128+
129+
const flowData = this.flow ?? { gitpodHost: this.hostService.gitpodHost, userId: this.sessionService.safeGetUserId() };
130+
this.telemetryService.sendUserFlowStatus(this.isSupportLocalSSH ? 'success' : 'failure', { ...flowData, flow: 'local_ssh_config', failureCode, useLocalAPP: String(Configuration.getUseLocalApp()) });
122131
}
123132

124133
private async configureSettings({ proxyScript, launcher }: { proxyScript: string; launcher: string }) {

0 commit comments

Comments
 (0)