Skip to content

Commit 426d5bf

Browse files
committed
Fix problems with changing local port and restoring
Fixes microsoft#92008
1 parent 361a544 commit 426d5bf

11 files changed

Lines changed: 49 additions & 28 deletions

File tree

src/vs/platform/remote/common/remoteAuthorityResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface ResolvedOptions {
1919

2020
export interface TunnelDescription {
2121
remoteAddress: { port: number, host: string };
22-
localAddress: string;
22+
localAddress: { port: number, host: string } | string;
2323
}
2424
export interface TunnelInformation {
2525
environmentTunnels?: TunnelDescription[];

src/vs/platform/remote/common/tunnel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface RemoteTunnel {
1515
readonly tunnelRemoteHost: string;
1616
readonly tunnelLocalPort?: number;
1717
readonly localAddress: string;
18-
dispose(): void;
18+
dispose(silent?: boolean): void;
1919
}
2020

2121
export interface TunnelOptions {

src/vs/vscode.proposed.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ declare module 'vscode' {
107107
export interface TunnelDescription {
108108
remoteAddress: { port: number, host: string };
109109
//The complete local address(ex. localhost:1234)
110-
localAddress: string;
110+
localAddress: { port: number, host: string } | string;
111111
}
112112

113113
export interface Tunnel extends TunnelDescription {

src/vs/workbench/api/browser/mainThreadTunnelService.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
77
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
88
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
9-
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
9+
import { IRemoteExplorerService, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
1010
import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel';
1111
import { Disposable } from 'vs/base/common/lifecycle';
1212
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
@@ -51,6 +51,10 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
5151
this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts());
5252
}
5353

54+
async $tunnelServiceReady(): Promise<void> {
55+
return this.remoteExplorerService.restore();
56+
}
57+
5458
async $setTunnelProvider(): Promise<void> {
5559
const tunnelProvider: ITunnelProvider = {
5660
forwardPort: (tunnelOptions: TunnelOptions) => {
@@ -60,9 +64,12 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
6064
return {
6165
tunnelRemotePort: tunnel.remoteAddress.port,
6266
tunnelRemoteHost: tunnel.remoteAddress.host,
63-
localAddress: tunnel.localAddress,
64-
dispose: () => {
65-
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port });
67+
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
68+
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
69+
dispose: (silent: boolean) => {
70+
if (!silent) {
71+
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port });
72+
}
6673
}
6774
};
6875
});

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
801801
$registerCandidateFinder(): Promise<void>;
802802
$setTunnelProvider(): Promise<void>;
803803
$setCandidateFilter(): Promise<void>;
804+
$tunnelServiceReady(): Promise<void>;
804805
}
805806

806807
export interface MainThreadTimelineShape extends IDisposable {

src/vs/workbench/api/common/extHostTunnelService.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
6+
import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
77
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
88
import * as vscode from 'vscode';
99
import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel';
1010
import { IDisposable } from 'vs/base/common/lifecycle';
1111
import { Emitter } from 'vs/base/common/event';
12+
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
1213

1314
export interface TunnelDto {
1415
remoteAddress: { port: number, host: string };
15-
localAddress: string;
16+
localAddress: { port: number, host: string } | string;
1617
}
1718

1819
export namespace TunnelDto {
@@ -42,6 +43,13 @@ export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IEx
4243
export class ExtHostTunnelService implements IExtHostTunnelService {
4344
_serviceBrand: undefined;
4445
onDidChangeTunnels: vscode.Event<void> = (new Emitter<void>()).event;
46+
private readonly _proxy: MainThreadTunnelServiceShape;
47+
48+
constructor(
49+
@IExtHostRpcService extHostRpc: IExtHostRpcService,
50+
) {
51+
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
52+
}
4553

4654
async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
4755
return undefined;
@@ -55,7 +63,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
5563
async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<boolean[]> {
5664
return candidates.map(() => true);
5765
}
58-
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
66+
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
67+
await this._proxy.$tunnelServiceReady();
68+
return { dispose: () => { } };
69+
}
5970
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined { return undefined; }
6071
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
6172
async $onDidTunnelsChange(): Promise<void> { }

src/vs/workbench/api/node/extHostTunnelService.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class ExtensionTunnel implements vscode.Tunnel {
2323
onDidDispose: Event<void> = this._onDispose.event;
2424

2525
constructor(
26-
public readonly remoteAddress: { port: number; host: string; },
27-
public readonly localAddress: string,
26+
public readonly remoteAddress: { port: number, host: string },
27+
public readonly localAddress: { port: number, host: string } | string,
2828
private readonly _dispose: () => void) { }
2929

3030
dispose(): void {
@@ -52,6 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
5252
this.registerCandidateFinder();
5353
}
5454
}
55+
5556
async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
5657
const tunnel = await this._proxy.$openTunnel(forward);
5758
if (tunnel) {
@@ -91,6 +92,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
9192
} else {
9293
this._forwardPortProvider = undefined;
9394
}
95+
await this._proxy.$tunnelServiceReady();
9496
return toDisposable(() => {
9597
this._forwardPortProvider = undefined;
9698
});

src/vs/workbench/contrib/remote/browser/tunnelView.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
159159
this._candidates.forEach(value => {
160160
const key = MakeAddress(value.host, value.port);
161161
if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) {
162-
candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail));
162+
candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, undefined, false, undefined, value.detail));
163163
}
164164
});
165165
return candidates;
@@ -375,14 +375,15 @@ interface ITunnelGroup {
375375

376376
class TunnelItem implements ITunnelItem {
377377
static createFromTunnel(tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) {
378-
return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description);
378+
return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description);
379379
}
380380

381381
constructor(
382382
public tunnelType: TunnelType,
383383
public remoteHost: string,
384384
public remotePort: number,
385385
public localAddress?: string,
386+
public localPort?: number,
386387
public closeable?: boolean,
387388
public name?: string,
388389
private _description?: string,
@@ -415,11 +416,6 @@ class TunnelItem implements ITunnelItem {
415416
}
416417
}
417418

418-
function isHostAndPort(address: string | undefined): boolean {
419-
const result = address ? address.match(/^(localhost|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|([0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+)):[0-9]+$/) : [];
420-
return (!!result && result.length > 0);
421-
}
422-
423419
export const TunnelTypeContextKey = new RawContextKey<TunnelType>('tunnelType', TunnelType.Add);
424420
export const TunnelCloseableContextKey = new RawContextKey<boolean>('tunnelCloseable', false);
425421
const TunnelViewFocusContextKey = new RawContextKey<boolean>('tunnelViewFocus', false);
@@ -577,7 +573,7 @@ export class TunnelPanel extends ViewPane {
577573
this.tunnelViewSelectionContext.set(item);
578574
this.tunnelTypeContext.set(item.tunnelType);
579575
this.tunnelCloseableContext.set(!!item.closeable);
580-
this.portChangableContextKey.set(isHostAndPort(item.localAddress));
576+
this.portChangableContextKey.set(!!item.localPort);
581577
} else {
582578
this.tunnelTypeContext.reset();
583579
this.tunnelViewSelectionContext.reset();
@@ -600,7 +596,7 @@ export class TunnelPanel extends ViewPane {
600596
this.tree!.setFocus([node]);
601597
this.tunnelTypeContext.set(node.tunnelType);
602598
this.tunnelCloseableContext.set(!!node.closeable);
603-
this.portChangableContextKey.set(isHostAndPort(node.localAddress));
599+
this.portChangableContextKey.set(!!node.localPort);
604600
} else {
605601
this.tunnelTypeContext.set(TunnelType.Add);
606602
this.tunnelCloseableContext.set(false);
@@ -1039,7 +1035,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
10391035
id: ChangeLocalPortAction.ID,
10401036
title: ChangeLocalPortAction.LABEL,
10411037
},
1042-
when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded)
1038+
when: ContextKeyExpr.and(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), PortChangableContextKey)
10431039
}));
10441040
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
10451041
group: '0_manage',

src/vs/workbench/services/remote/common/remoteExplorerService.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface ITunnelItem {
2929
remoteHost: string;
3030
remotePort: number;
3131
localAddress?: string;
32+
localPort?: number;
3233
name?: string;
3334
closeable?: boolean;
3435
description?: string;
@@ -114,11 +115,9 @@ export class TunnelModel extends Disposable {
114115
this._onClosePort.fire(address);
115116
}
116117
}));
117-
118-
this.restoreForwarded();
119118
}
120119

121-
private async restoreForwarded() {
120+
async restoreForwarded() {
122121
if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
123122
const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE);
124123
if (tunnelsString) {
@@ -181,7 +180,7 @@ export class TunnelModel extends Disposable {
181180
this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), {
182181
remoteHost: tunnel.remoteAddress.host,
183182
remotePort: tunnel.remoteAddress.port,
184-
localAddress: tunnel.localAddress,
183+
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
185184
closeable: false
186185
});
187186
});
@@ -238,6 +237,7 @@ export interface IRemoteExplorerService {
238237
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
239238
setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): IDisposable;
240239
refresh(): Promise<void>;
240+
restore(): Promise<void>;
241241
}
242242

243243
class RemoteExplorerService implements IRemoteExplorerService {
@@ -328,6 +328,10 @@ class RemoteExplorerService implements IRemoteExplorerService {
328328
refresh(): Promise<void> {
329329
return this.tunnelModel.refresh();
330330
}
331+
332+
restore(): Promise<void> {
333+
return this.tunnelModel.restoreForwarded();
334+
}
331335
}
332336

333337
registerSingleton(IRemoteExplorerService, RemoteExplorerService, true);

src/vs/workbench/services/remote/common/tunnelService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
101101
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
102102
if (tunnel.refcount <= 0) {
103103
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
104-
tunnel.dispose();
104+
tunnel.dispose(true);
105105
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
106106
});
107107
if (this._tunnels.has(remoteHost)) {

0 commit comments

Comments
 (0)