Skip to content

Commit 4ec45fc

Browse files
authored
Lock file before copy proxy scripts to reduce race condition IDE-141 (#71)
* Initialize when needed * Lock file before copy content
1 parent 292e05b commit 4ec45fc

File tree

4 files changed

+67
-7
lines changed

4 files changed

+67
-7
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"@types/js-yaml": "^4.0.5",
149149
"@types/mocha": "^9.1.1",
150150
"@types/node": "16.x",
151+
"@types/proper-lockfile": "^4.1.2",
151152
"@types/ps-node": "^0.1.1",
152153
"@types/semver": "^7.3.10",
153154
"@types/ssh2": "^0.5.52",
@@ -193,6 +194,7 @@
193194
"node-fetch-commonjs": "^3.2.4",
194195
"pkce-challenge": "^3.0.0",
195196
"prom-client": "^14.1.1",
197+
"proper-lockfile": "^4.1.2",
196198
"protobufjs": "^7.2.2",
197199
"ps-node": "^0.1.6",
198200
"semver": "^7.3.7",

src/remoteConnector.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ export class RemoteConnector extends Disposable {
692692
// we need to update the remote ssh config first, since another call is too late for local-ssh
693693
await this.updateRemoteSSHConfig(true, undefined);
694694
this.localSSHService.flow = sshFlow;
695+
this.localSSHService.prepareInitialize();
695696
await this.localSSHService.initialized;
696697
if (!this.localSSHService.isSupportLocalSSH) {
697698
this.logService.error('Local SSH is not supported on this platform');

src/services/localSSHService.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as vscode from 'vscode';
77
import fsp from 'fs/promises';
8+
import lockfile from 'proper-lockfile';
89
import { Disposable } from '../common/dispose';
910
import { ILogService } from './logService';
1011
import { Configuration } from '../configuration';
@@ -20,13 +21,14 @@ export interface ILocalSSHService {
2021
flow?: UserFlowTelemetryProperties;
2122
isSupportLocalSSH: boolean;
2223
initialized: Promise<void>;
24+
prepareInitialize: () => void;
2325
}
2426

25-
type FailedToInitializeCode = 'Unknown' | string;
27+
type FailedToInitializeCode = 'Unknown' | 'LockFailed' | string;
2628

2729
export class LocalSSHService extends Disposable implements ILocalSSHService {
2830
public isSupportLocalSSH: boolean = false;
29-
public initialized: Promise<void>;
31+
public initialized!: Promise<void>;
3032
public flow?: UserFlowTelemetryProperties;
3133
constructor(
3234
private readonly context: vscode.ExtensionContext,
@@ -36,9 +38,10 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
3638
private readonly logService: ILogService,
3739
) {
3840
super();
41+
}
3942

43+
prepareInitialize() {
4044
this.initialized = this.initialize();
41-
4245
this._register(vscode.workspace.onDidChangeConfiguration(async e => {
4346
if (
4447
e.affectsConfiguration('gitpod.lsshExtensionIpcPort') ||
@@ -62,10 +65,13 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
6265
private async initialize() {
6366
let failureCode: FailedToInitializeCode | undefined;
6467
const useLocalAPP = String(Configuration.getUseLocalApp());
68+
const lockFolder = vscode.Uri.joinPath(this.context.globalStorageUri, 'initialize.lock');
6569
try {
66-
const locations = await this.copyProxyScript();
67-
await this.configureSettings(locations);
68-
this.isSupportLocalSSH = true;
70+
await this.lock(lockFolder.fsPath, async () => {
71+
const locations = await this.copyProxyScript();
72+
await this.configureSettings(locations);
73+
this.isSupportLocalSSH = true;
74+
});
6975
} catch (e) {
7076
this.logService.error(e, 'failed to initialize');
7177
failureCode = 'Unknown';
@@ -121,4 +127,29 @@ export class LocalSSHService extends Disposable implements ILocalSSHService {
121127
await vscode.workspace.fs.copy(fileUri, destUri, { overwrite: true });
122128
return destUri.fsPath;
123129
}
130+
131+
private async lock(path: string, cb: () => Promise<void>) {
132+
let release: () => Promise<void>;
133+
try {
134+
release = await lockfile.lock(path, {
135+
stale: 1000 * 10, // 10s
136+
retries: {
137+
retries: 3,
138+
factor: 1,
139+
minTimeout: 1 * 1000,
140+
randomize: true,
141+
},
142+
realpath: false,
143+
});
144+
} catch (e) {
145+
throw new WrapError('Failed to lock file', e, 'LockFailed');
146+
}
147+
try {
148+
await cb();
149+
} catch (e) {
150+
throw e;
151+
} finally {
152+
await release();
153+
}
154+
}
124155
}

yarn.lock

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,13 @@
456456
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
457457
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
458458

459+
"@types/proper-lockfile@^4.1.2":
460+
version "4.1.2"
461+
resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-4.1.2.tgz#49537cee7134055ee13a1833b76a1c298f39bb26"
462+
integrity sha512-kd4LMvcnpYkspDcp7rmXKedn8iJSCoa331zRRamUp5oanKt/CefbEGPQP7G89enz7sKD4bvsr8mHSsC8j5WOvA==
463+
dependencies:
464+
"@types/retry" "*"
465+
459466
"@types/ps-node@^0.1.1":
460467
version "0.1.1"
461468
resolved "https://registry.yarnpkg.com/@types/ps-node/-/ps-node-0.1.1.tgz#fbbb7a0c55bf4e945756c023ccd205576f6177d2"
@@ -470,6 +477,11 @@
470477
"@types/scheduler" "*"
471478
csstype "^3.0.2"
472479

480+
"@types/retry@*":
481+
version "0.12.2"
482+
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a"
483+
integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==
484+
473485
"@types/scheduler@*":
474486
version "0.16.2"
475487
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@@ -3173,6 +3185,15 @@ prom-client@^14.1.1:
31733185
dependencies:
31743186
tdigest "^0.1.1"
31753187

3188+
proper-lockfile@^4.1.2:
3189+
version "4.1.2"
3190+
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
3191+
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
3192+
dependencies:
3193+
graceful-fs "^4.2.4"
3194+
retry "^0.12.0"
3195+
signal-exit "^3.0.2"
3196+
31763197
protobufjs@^6.11.3, protobufjs@^6.8.8:
31773198
version "6.11.3"
31783199
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74"
@@ -3414,6 +3435,11 @@ resolve@^1.9.0:
34143435
path-parse "^1.0.7"
34153436
supports-preserve-symlinks-flag "^1.0.0"
34163437

3438+
retry@^0.12.0:
3439+
version "0.12.0"
3440+
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
3441+
integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
3442+
34173443
reusify@^1.0.4:
34183444
version "1.0.4"
34193445
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -3573,7 +3599,7 @@ side-channel@^1.0.4:
35733599
get-intrinsic "^1.0.2"
35743600
object-inspect "^1.9.0"
35753601

3576-
signal-exit@^3.0.0, signal-exit@^3.0.3:
3602+
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
35773603
version "3.0.7"
35783604
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
35793605
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==

0 commit comments

Comments
 (0)