Catch errors in tunnel providers and log

This commit is contained in:
Alex Ross 2021-01-20 17:26:16 +01:00
parent 947626dfa4
commit 38c051bf86
3 changed files with 64 additions and 27 deletions

View file

@ -42,6 +42,24 @@ export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
}
export interface ITunnel {
remoteAddress: { port: number, host: string };
/**
* The complete local address(ex. localhost:1234)
*/
localAddress: string;
public?: boolean;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/
onDidDispose: Event<void>;
dispose(): Promise<void> | void;
}
export interface ITunnelService {
readonly _serviceBrand: undefined;

View file

@ -15,13 +15,13 @@ import * as fs from 'fs';
import * as pfs from 'vs/base/node/pfs';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { promisify } from 'util';
import { MovingAverage } from 'vs/base/common/numbers';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
@ -142,7 +142,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly logService: ILogService
) {
super();
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
@ -234,16 +235,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
if (this._forwardPortProvider) {
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => {
try {
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
if (providedPort !== undefined) {
const tunnel = await providedPort;
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
}
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
return TunnelDto.fromApiTunnel(tunnel);
}
} catch (e) {
this.logService.trace('$forwardPort: tunnel provider error');
}
}
return undefined;

View file

@ -3,44 +3,59 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution {
constructor(
@ITunnelService tunnelService: ITunnelService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IOpenerService openerService: IOpenerService,
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService,
@ILogService logService: ILogService
) {
super();
const tunnelFactory = environmentService.options?.tunnelProvider?.tunnelFactory;
if (tunnelFactory) {
this._register(tunnelService.setTunnelProvider({
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel> | undefined => {
const tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
if (!tunnelPromise) {
return undefined;
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined => {
let tunnelPromise: Promise<ITunnel> | undefined;
try {
tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
} catch (e) {
logService.trace('tunnelFactory: tunnel provider error');
}
return new Promise(resolve => {
tunnelPromise.then(async (tunnel) => {
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
const remoteTunnel: RemoteTunnel = {
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
public: !!tunnel.public,
dispose: async () => { await tunnel.dispose(); }
};
resolve(remoteTunnel);
});
return new Promise(async (resolve) => {
if (!tunnelPromise) {
resolve(undefined);
return;
}
let tunnel: ITunnel;
try {
tunnel = await tunnelPromise;
} catch (e) {
logService.trace('tunnelFactory: tunnel provider promise error');
resolve(undefined);
return;
}
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
const remoteTunnel: RemoteTunnel = {
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
public: !!tunnel.public,
dispose: async () => { await tunnel.dispose(); }
};
resolve(remoteTunnel);
});
}
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false, public: false }));