Catch errors in tunnel providers and log
This commit is contained in:
parent
947626dfa4
commit
38c051bf86
3 changed files with 64 additions and 27 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }));
|
||||
|
|
Loading…
Reference in a new issue