From cf7ddbb51d0eaaed6dc25d2df137594254f3b790 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 6 Feb 2024 19:25:58 +0900 Subject: [PATCH] fix: use legacy server as default with additional warnings (#204377) * ci: switch to glibc 2.17 remote server * chore: signal user about unsupported connection * chore: address review comments * chore: update nodejs build * chore: bump distro * chore: lower the minimum requirements * fix: glibc version check * chore: remove explicit connection disposal --- build/.cachesalt | 2 +- build/azure-pipelines/linux/install.sh | 5 - .../linux/product-build-linux.yml | 58 ++---- .../linux/verify-glibc-requirements.sh | 44 +++++ build/gulpfile.reh.js | 9 +- build/npm/postinstall.js | 23 ++- cli/src/util/prereqs.rs | 4 +- package.json | 2 +- remote/.yarnrc | 2 +- .../bin/helpers/check-requirements-linux.sh | 2 +- .../remote/common/remoteAgentEnvironment.ts | 1 + .../server/node/remoteAgentEnvironmentImpl.ts | 12 +- .../browser/parts/banner/bannerPart.ts | 12 +- .../remote/browser/remote.contribution.ts | 2 + .../remote/browser/remoteConnectionHealth.ts | 183 ++++++++++++++++++ .../remote/common/remote.contribution.ts | 94 +-------- .../services/banner/browser/bannerService.ts | 1 + .../common/remoteAgentEnvironmentChannel.ts | 4 +- 18 files changed, 298 insertions(+), 162 deletions(-) create mode 100755 build/azure-pipelines/linux/verify-glibc-requirements.sh create mode 100644 src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts diff --git a/build/.cachesalt b/build/.cachesalt index 4f3f91b0edfe4..89591977f282f 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2024-01-29T19:26:27.993Z +2024-02-05T09:34:15.476Z diff --git a/build/azure-pipelines/linux/install.sh b/build/azure-pipelines/linux/install.sh index 16fefd2e7546c..57f58763ccaab 100755 --- a/build/azure-pipelines/linux/install.sh +++ b/build/azure-pipelines/linux/install.sh @@ -34,11 +34,6 @@ if [ "$npm_config_arch" == "x64" ]; then export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" export LDFLAGS="-stdlib=libc++ --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -fuse-ld=lld -flto=thin -L$PWD/.build/libcxx-objects -lc++abi -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/lib/x86_64-linux-gnu -Wl,--lto-O0" - # Set compiler toolchain for remote server - export VSCODE_REMOTE_CC=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc - export VSCODE_REMOTE_CXX=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-g++ - export VSCODE_REMOTE_CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" - export VSCODE_REMOTE_LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/lib/x86_64-linux-gnu" elif [ "$npm_config_arch" == "arm64" ]; then # Set compiler toolchain for client native modules and remote server export CC=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 848c47fe82328..e4b4fab899b3c 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -121,53 +121,23 @@ steps: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" + ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) + ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-arm32v7 displayName: Install dependencies (non-OSS) condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e + - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: | + set -e - TRIPLE="x86_64-linux-gnu" - if [ "$VSCODE_ARCH" == "arm64" ]; then - TRIPLE="aarch64-linux-gnu" - elif [ "$VSCODE_ARCH" == "armhf" ]; then - TRIPLE="arm-rpi-linux-gnueabihf" - fi - - # Get all files with .node extension from remote/node_modules folder - files=$(find remote/node_modules -name "*.node") - - # Check if any file has dependency of GLIBC > 2.28 or GLIBCXX > 3.4.25 - for file in $files; do - glibc_version="2.28" - glibcxx_version="3.4.25" - while IFS= read -r line; do - if [[ $line == *"GLIBC_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') - version=${version#*_} - if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then - glibc_version=$version - fi - elif [[ $line == *"GLIBCXX_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') - version=${version#*_} - if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then - glibcxx_version=$version - fi - fi - done < <("$PWD/.build/sysroots/$TRIPLE/$TRIPLE/bin/objdump" -T "$file") - - if [[ "$glibc_version" != "2.28" ]]; then - echo "Error: File $file has dependency on GLIBC > 2.28" - exit 1 - fi - if [[ "$glibcxx_version" != "3.4.25" ]]; then - echo "Error: File $file has dependency on GLIBCXX > 3.4.25" - exit 1 - fi - done - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules + EXPECTED_GLIBC_VERSION="2.17" \ + EXPECTED_GLIBCXX_VERSION="3.4.19" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules - script: node build/azure-pipelines/distro/mixin-npm condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) @@ -248,6 +218,7 @@ steps: - script: | set -e + export VSCODE_NODE_GLIBC='-glibc-2.17' yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" @@ -260,6 +231,7 @@ steps: - script: | set -e + export VSCODE_NODE_GLIBC='-glibc-2.17' yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" diff --git a/build/azure-pipelines/linux/verify-glibc-requirements.sh b/build/azure-pipelines/linux/verify-glibc-requirements.sh new file mode 100755 index 0000000000000..f07c0ba71b0e4 --- /dev/null +++ b/build/azure-pipelines/linux/verify-glibc-requirements.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -e + +TRIPLE="x86_64-linux-gnu" +if [ "$VSCODE_ARCH" == "arm64" ]; then + TRIPLE="aarch64-linux-gnu" +elif [ "$VSCODE_ARCH" == "armhf" ]; then + TRIPLE="arm-rpi-linux-gnueabihf" +fi + +# Get all files with .node extension from remote/node_modules folder +files=$(find remote/node_modules -name "*.node" -not -path "*prebuilds*") + +echo "Verifying requirements for files: $files" + +for file in $files; do + glibc_version="$EXPECTED_GLIBC_VERSION" + glibcxx_version="$EXPECTED_GLIBCXX_VERSION" + while IFS= read -r line; do + if [[ $line == *"GLIBC_"* ]]; then + version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=${version#*_} + if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then + glibc_version=$version + fi + elif [[ $line == *"GLIBCXX_"* ]]; then + version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=${version#*_} + if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then + glibcxx_version=$version + fi + fi + done < <("$PWD/.build/sysroots/$TRIPLE/$TRIPLE/bin/objdump" -T "$file") + + if [[ "$glibc_version" != "$EXPECTED_GLIBC_VERSION" ]]; then + echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION" + exit 1 + fi + if [[ "$glibcxx_version" != "$EXPECTED_GLIBCXX_VERSION" ]]; then + echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION" + exit 1 + fi +done diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index df18cf06aa9a3..595d0ce1f434b 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -125,7 +125,7 @@ function getNodeVersion() { return { nodeVersion, internalNodeVersion }; } -function getNodeChecksum(nodeVersion, platform, arch) { +function getNodeChecksum(nodeVersion, platform, arch, glibcPrefix) { let expectedName; switch (platform) { case 'win32': @@ -135,7 +135,7 @@ function getNodeChecksum(nodeVersion, platform, arch) { case 'darwin': case 'alpine': case 'linux': - expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; + expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; break; } @@ -193,7 +193,8 @@ function nodejs(platform, arch) { log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); - const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch); + const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; + const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch, glibcPrefix); if (checksumSha256) { log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); @@ -210,7 +211,7 @@ function nodejs(platform, arch) { case 'darwin': case 'linux': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index cce0f46037881..72dd74f8986cc 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -53,10 +53,14 @@ function yarnInstall(dir, opts) { console.log(`Installing dependencies in ${dir} inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); opts.cwd = root; - if (process.env['npm_config_arch'] === 'arm64') { + if (process.env['npm_config_arch'] === 'arm64' || process.env['npm_config_arch'] === 'arm') { run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); } - run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + if (process.env['npm_config_arch'] === 'arm') { + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/home/builduser`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/home/builduser/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + } else { + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + } run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${dir}/node_modules`], opts); } else { console.log(`Installing dependencies in ${dir}...`); @@ -102,8 +106,19 @@ for (let dir of dirs) { if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { // node modules used by vscode server const env = { ...process.env }; - if (process.env['VSCODE_REMOTE_CC']) { env['CC'] = process.env['VSCODE_REMOTE_CC']; } - if (process.env['VSCODE_REMOTE_CXX']) { env['CXX'] = process.env['VSCODE_REMOTE_CXX']; } + if (process.env['VSCODE_REMOTE_CC']) { + env['CC'] = process.env['VSCODE_REMOTE_CC']; + } else { + delete env['CC']; + } + if (process.env['VSCODE_REMOTE_CXX']) { + env['CXX'] = process.env['VSCODE_REMOTE_CXX']; + } else { + delete env['CXX']; + } + if (process.env['CXXFLAGS']) { delete env['CXXFLAGS']; } + if (process.env['CFLAGS']) { delete env['CFLAGS']; } + if (process.env['LDFLAGS']) { delete env['LDFLAGS']; } if (process.env['VSCODE_REMOTE_CXXFLAGS']) { env['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } if (process.env['VSCODE_REMOTE_LDFLAGS']) { env['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index 3bf2934e25542..b22fd469facdd 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -20,8 +20,8 @@ lazy_static! { static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap(); static ref LIBSTD_CXX_VERSION_RE: BinRegex = BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap(); - static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 25); - static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 28, 0); + static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 19); + static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0); } const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; diff --git a/package.json b/package.json index e5949c93c22eb..ecf12d79edc03 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "ddced7c51be56108bf9f4bc3e3481a8673aa5031", + "distro": "205c07946b7d7629f85cf332acfcfc8a76f23f6d", "author": { "name": "Microsoft Corporation" }, diff --git a/remote/.yarnrc b/remote/.yarnrc index adbe2d2a5f47f..cac528fd2dd2c 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" target "18.17.1" -ms_build_id "252256" +ms_build_id "255375" runtime "node" build_from_source "true" diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 44240f7e536a5..1b9199fd03930 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -126,5 +126,5 @@ fi if [ "$found_required_glibc" = "0" ] || [ "$found_required_glibcxx" = "0" ]; then echo "Error: Missing required dependencies. Please refer to our FAQ https://aka.ms/vscode-remote/faq/old-linux for additional information." # Custom exit code based on https://tldp.org/LDP/abs/html/exitcodes.html - exit 99 + #exit 99 fi diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index d83efeb9408bc..de9a7559ad1f8 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -27,6 +27,7 @@ export interface IRemoteAgentEnvironment { all: IUserDataProfile[]; home: URI; }; + isUnsupportedGlibc: boolean; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/server/node/remoteAgentEnvironmentImpl.ts b/src/vs/server/node/remoteAgentEnvironmentImpl.ts index b5aa198663ed9..b9861444cafe5 100644 --- a/src/vs/server/node/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/node/remoteAgentEnvironmentImpl.ts @@ -96,6 +96,15 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { if (profile && !this._userDataProfilesService.profiles.some(p => p.id === profile)) { await this._userDataProfilesService.createProfile(profile, profile); } + type ProcessWithGlibc = NodeJS.Process & { + glibcVersion?: string; + }; + let isUnsupportedGlibc = false; + if (process.platform === 'linux') { + const glibcVersion = (process as ProcessWithGlibc).glibcVersion; + const minorVersion = glibcVersion ? parseInt(glibcVersion.split('.')[1]) : 28; + isUnsupportedGlibc = (minorVersion <= 27); + } return { pid: process.pid, connectionToken: (this._connectionToken.type !== ServerConnectionTokenType.None ? this._connectionToken.value : ''), @@ -114,7 +123,8 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { profiles: { home: this._userDataProfilesService.profilesHome, all: [...this._userDataProfilesService.profiles].map(profile => ({ ...profile })) - } + }, + isUnsupportedGlibc }; } diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index ccf73b4808e01..3725e594c9733 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -222,11 +222,13 @@ export class BannerPart extends Part implements IBannerService { } // Action - const actionBarContainer = append(this.element, $('div.action-container')); - this.actionBar = this._register(new ActionBar(actionBarContainer)); - const closeAction = this._register(new Action('banner.close', 'Close Banner', ThemeIcon.asClassName(widgetClose), true, () => this.close(item))); - this.actionBar.push(closeAction, { icon: true, label: false }); - this.actionBar.setFocusable(false); + if (!item.disableCloseAction) { + const actionBarContainer = append(this.element, $('div.action-container')); + this.actionBar = this._register(new ActionBar(actionBarContainer)); + const closeAction = this._register(new Action('banner.close', 'Close Banner', ThemeIcon.asClassName(widgetClose), true, () => this.close(item))); + this.actionBar.push(closeAction, { icon: true, label: false }); + this.actionBar.setFocusable(false); + } this.setVisibility(true); this.item = item; diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts index 83aeb5466d5e3..4689c816624d4 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts @@ -11,6 +11,7 @@ import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/browser/t import { RemoteAgentConnectionStatusListener, RemoteMarkers } from 'vs/workbench/contrib/remote/browser/remote'; import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator'; import { AutomaticPortForwarding, ForwardedPortsView, PortRestore } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; +import { InitialRemoteConnectionHealthContribution } from 'vs/workbench/contrib/remote/browser/remoteConnectionHealth'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchPhase.BlockRestore); @@ -21,3 +22,4 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteMarkers, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts new file mode 100644 index 0000000000000..c75b9c543f7e5 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts @@ -0,0 +1,183 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { localize } from 'vs/nls'; +import { isWeb } from 'vs/base/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Codicon } from 'vs/base/common/codicons'; +import Severity from 'vs/base/common/severity'; + + +const REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY = 'remote.unsupportedConnectionChoice'; + +export class InitialRemoteConnectionHealthContribution implements IWorkbenchContribution { + + constructor( + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IBannerService private readonly bannerService: IBannerService, + @IDialogService private readonly dialogService: IDialogService, + @IOpenerService private readonly openerService: IOpenerService, + @IHostService private readonly hostService: IHostService, + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + ) { + if (this._environmentService.remoteAuthority) { + this._checkInitialRemoteConnectionHealth(); + } + } + + private async _confirmConnection(): Promise { + const enum ConnectionChoice { + Allow = 1, + LearnMore = 2, + Cancel = 0 + } + + const { result, checkboxChecked } = await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('unsupportedGlibcWarning', "You are about to connect to an OS that is unsupported by {0}", this.productService.nameLong), + buttons: [ + { + label: localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), + run: () => ConnectionChoice.Allow + }, + { + label: localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More"), + run: async () => { await this.openerService.open('https://aka.ms/vscode-remote/faq/old-linux'); return ConnectionChoice.LearnMore; } + } + ], + cancelButton: { + run: () => ConnectionChoice.Cancel + }, + checkbox: { + label: localize('remember', "Do not ask me again"), + } + }); + + if (result === ConnectionChoice.LearnMore) { + return await this._confirmConnection(); + } + + const allowed = result === ConnectionChoice.Allow; + if (checkboxChecked) { + this.storageService.store(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, allowed, StorageScope.PROFILE, StorageTarget.MACHINE); + } + + return allowed; + } + + private async _checkInitialRemoteConnectionHealth(): Promise { + try { + const environment = await this._remoteAgentService.getRawEnvironment(); + + if (environment && environment.isUnsupportedGlibc) { + let allowed = this.storageService.getBoolean(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, StorageScope.PROFILE); + if (allowed === undefined) { + allowed = await this._confirmConnection(); + } + if (allowed) { + const actions = [ + { + label: localize('unsupportedGlibcBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-remote/faq/old-linux' + } + ]; + this.bannerService.show({ + id: 'unsupportedGlibcWarning.banner', + message: localize('unsupportedGlibcWarning.banner', "You are connected to an OS that is unsupported by {0}.", this.productService.nameLong), + actions, + icon: Codicon.warning, + disableCloseAction: true + }); + } else { + this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: null }); + return; + } + } + + type RemoteConnectionSuccessClassification = { + owner: 'alexdima'; + comment: 'The initial connection succeeded'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + }; + type RemoteConnectionSuccessEvent = { + web: boolean; + connectionTimeMs: number | undefined; + remoteName: string | undefined; + }; + this._telemetryService.publicLog2('remoteConnectionSuccess', { + web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), + remoteName: getRemoteName(this._environmentService.remoteAuthority) + }); + + await this._measureExtHostLatency(); + + } catch (err) { + + type RemoteConnectionFailureClassification = { + owner: 'alexdima'; + comment: 'The initial connection failed'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; + message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; + }; + type RemoteConnectionFailureEvent = { + web: boolean; + remoteName: string | undefined; + connectionTimeMs: number | undefined; + message: string; + }; + this._telemetryService.publicLog2('remoteConnectionFailure', { + web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), + remoteName: getRemoteName(this._environmentService.remoteAuthority), + message: err ? err.message : '' + }); + + } + } + + private async _measureExtHostLatency() { + const measurement = await remoteConnectionLatencyMeasurer.measure(this._remoteAgentService); + if (measurement === undefined) { + return; + } + + type RemoteConnectionLatencyClassification = { + owner: 'connor4312'; + comment: 'The latency to the remote extension host'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; + latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; + }; + type RemoteConnectionLatencyEvent = { + web: boolean; + remoteName: string | undefined; + latencyMs: number; + }; + + this._telemetryService.publicLog2('remoteConnectionLatency', { + web: isWeb, + remoteName: getRemoteName(this._environmentService.remoteAuthority), + latencyMs: measurement.current + }); + } +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 1b76477c4b7e8..c66080cacec69 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -9,7 +9,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { OperatingSystem, isWeb, OS } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; -import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -24,8 +24,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { PersistentConnection } from 'vs/platform/remote/common/remoteAgentConnection'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { RemoteLoggerChannelClient } from 'vs/platform/log/common/logIpc'; @@ -144,100 +142,10 @@ class RemoteInvalidWorkspaceDetector extends Disposable implements IWorkbenchCon } } -class InitialRemoteConnectionHealthContribution implements IWorkbenchContribution { - - constructor( - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - ) { - if (this._environmentService.remoteAuthority) { - this._checkInitialRemoteConnectionHealth(); - } - } - - private async _checkInitialRemoteConnectionHealth(): Promise { - try { - await this._remoteAgentService.getRawEnvironment(); - - type RemoteConnectionSuccessClassification = { - owner: 'alexdima'; - comment: 'The initial connection succeeded'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; - connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; - }; - type RemoteConnectionSuccessEvent = { - web: boolean; - connectionTimeMs: number | undefined; - remoteName: string | undefined; - }; - this._telemetryService.publicLog2('remoteConnectionSuccess', { - web: isWeb, - connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), - remoteName: getRemoteName(this._environmentService.remoteAuthority) - }); - - await this._measureExtHostLatency(); - - } catch (err) { - - type RemoteConnectionFailureClassification = { - owner: 'alexdima'; - comment: 'The initial connection failed'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; - connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; - message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; - }; - type RemoteConnectionFailureEvent = { - web: boolean; - remoteName: string | undefined; - connectionTimeMs: number | undefined; - message: string; - }; - this._telemetryService.publicLog2('remoteConnectionFailure', { - web: isWeb, - connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), - remoteName: getRemoteName(this._environmentService.remoteAuthority), - message: err ? err.message : '' - }); - - } - } - - private async _measureExtHostLatency() { - const measurement = await remoteConnectionLatencyMeasurer.measure(this._remoteAgentService); - if (measurement === undefined) { - return; - } - - type RemoteConnectionLatencyClassification = { - owner: 'connor4312'; - comment: 'The latency to the remote extension host'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; - latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; - }; - type RemoteConnectionLatencyEvent = { - web: boolean; - remoteName: string | undefined; - latencyMs: number; - }; - - this._telemetryService.publicLog2('remoteConnectionLatency', { - web: isWeb, - remoteName: getRemoteName(this._environmentService.remoteAuthority), - latencyMs: measurement.current - }); - } -} - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Restored); registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchPhase.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); const enableDiagnostics = true; diff --git a/src/vs/workbench/services/banner/browser/bannerService.ts b/src/vs/workbench/services/banner/browser/bannerService.ts index 639b1b2ce81e9..d8560ce135eb9 100644 --- a/src/vs/workbench/services/banner/browser/bannerService.ts +++ b/src/vs/workbench/services/banner/browser/bannerService.ts @@ -16,6 +16,7 @@ export interface IBannerItem { readonly actions?: ILinkDescriptor[]; readonly ariaLabel?: string; readonly onClose?: () => void; + readonly disableCloseAction?: boolean; } export const IBannerService = createDecorator('bannerService'); diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index c5d56b6dffe95..01e7c293a8cf8 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -43,6 +43,7 @@ export interface IRemoteAgentEnvironmentDTO { all: UriDto; home: UriComponents; }; + isUnsupportedGlibc: boolean; } export class RemoteExtensionEnvironmentChannelClient { @@ -70,7 +71,8 @@ export class RemoteExtensionEnvironmentChannelClient { arch: data.arch, marks: data.marks, useHostProxy: data.useHostProxy, - profiles: revive(data.profiles) + profiles: revive(data.profiles), + isUnsupportedGlibc: data.isUnsupportedGlibc }; }