Add hybrid automatic port detection (#178687)

* Hybrid port detection

* Use correct source for autoforwarded

* Watch for port closure in output forwarder

* Update setting description
This commit is contained in:
Alex Ross 2023-03-30 15:23:27 +02:00 committed by GitHub
parent fdc9c7cccb
commit d4d0d2a3e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 9 deletions

View file

@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol';
import { TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, ProvidedPortAttributes, PortAttributesProvider, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
@ -42,7 +42,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
private processFindingEnabled(): boolean {
return (!!this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING) || this.tunnelService.hasTunnelProvider)
&& (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS);
&& (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) !== PORT_AUTO_SOURCE_SETTING_OUTPUT);
}
async $setRemoteTunnelService(processId: number): Promise<void> {

View file

@ -6,7 +6,7 @@ import * as nls from 'vs/nls';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Extensions, IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
import { Attributes, AutoTunnelSource, IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { Attributes, AutoTunnelSource, IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_HYBRID, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction, openPreviewEnabledContext } from 'vs/workbench/contrib/remote/browser/tunnelView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -202,7 +202,10 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon
} else {
const useProc = () => (configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS);
if (useProc()) {
this._register(new ProcAutomaticPortForwarding(configurationService, remoteExplorerService, notificationService,
this._register(new ProcAutomaticPortForwarding(false, configurationService, remoteExplorerService, notificationService,
openerService, externalOpenerService, tunnelService, hostService, logService, contextKeyService));
} else if (configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_HYBRID) {
this._register(new ProcAutomaticPortForwarding(true, configurationService, remoteExplorerService, notificationService,
openerService, externalOpenerService, tunnelService, hostService, logService, contextKeyService));
}
this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService,
@ -433,6 +436,10 @@ class OutputAutomaticPortForwarding extends Disposable {
this.tryStartStopUrlFinder();
}));
this.tryStartStopUrlFinder();
if (configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_HYBRID) {
this._register(this.tunnelService.onTunnelClosed(tunnel => this.notifier.hide([tunnel.port])));
}
}
private tryStartStopUrlFinder() {
@ -484,6 +491,7 @@ class ProcAutomaticPortForwarding extends Disposable {
private portsFeatures: IDisposable | undefined;
constructor(
private readonly unforwardOnly: boolean,
private readonly configurationService: IConfigurationService,
readonly remoteExplorerService: IRemoteExplorerService,
readonly notificationService: INotificationService,
@ -613,12 +621,24 @@ class ProcAutomaticPortForwarding extends Disposable {
private async handleCandidateUpdate(removed: Map<string, { host: string; port: number }>) {
const removedPorts: number[] = [];
let autoForwarded: Set<string>;
if (this.unforwardOnly) {
autoForwarded = new Set();
for (const entry of this.remoteExplorerService.tunnelModel.forwarded.entries()) {
if (entry[1].source.source === TunnelSource.Auto) {
autoForwarded.add(entry[0]);
}
}
} else {
autoForwarded = this.autoForwarded;
}
for (const removedPort of removed) {
const key = removedPort[0];
const value = removedPort[1];
if (this.autoForwarded.has(key)) {
if (autoForwarded.has(key)) {
await this.remoteExplorerService.close(value);
this.autoForwarded.delete(key);
autoForwarded.delete(key);
removedPorts.push(value.port);
} else if (this.notifiedOnly.has(key)) {
this.notifiedOnly.delete(key);
@ -628,6 +648,10 @@ class ProcAutomaticPortForwarding extends Disposable {
}
}
if (this.unforwardOnly) {
return;
}
if (removedPorts.length > 0) {
await this.notifier.hide(removedPorts);
}

View file

@ -324,11 +324,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
},
'remote.autoForwardPortsSource': {
type: 'string',
markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'),
enum: ['process', 'output'],
markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` and `hybrid` options have no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'),
enum: ['process', 'output', 'hybrid'],
enumDescriptions: [
localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."),
localize('remote.autoForwardPortsSource.output', "Ports will be automatically forwarded when discovered by reading terminal and debug output. Not all processes that use ports will print to the integrated terminal or debug console, so some ports will be missed. Ports forwarded based on output will not be \"un-forwarded\" until reload or until the port is closed by the user in the Ports view.")
localize('remote.autoForwardPortsSource.output', "Ports will be automatically forwarded when discovered by reading terminal and debug output. Not all processes that use ports will print to the integrated terminal or debug console, so some ports will be missed. Ports forwarded based on output will not be \"un-forwarded\" until reload or until the port is closed by the user in the Ports view."),
localize('remote.autoForwardPortsSource.hybrid', "Ports will be automatically forwarded when discovered by reading terminal and debug output. Not all processes that use ports will print to the integrated terminal or debug console, so some ports will be missed. Ports will be \"un-forwarded\" by watching for processes that listen on that port to be terminated.")
],
default: 'process'
},

View file

@ -35,6 +35,7 @@ export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts';
export const PORT_AUTO_SOURCE_SETTING = 'remote.autoForwardPortsSource';
export const PORT_AUTO_SOURCE_SETTING_PROCESS = 'process';
export const PORT_AUTO_SOURCE_SETTING_OUTPUT = 'output';
export const PORT_AUTO_SOURCE_SETTING_HYBRID = 'hybrid';
export enum TunnelType {
Candidate = 'Candidate',