From 314bf8ac9e7b2ea250d69531910640fe187d09c1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 2 Nov 2023 15:10:18 +1100 Subject: [PATCH 1/2] Add a timeout for telemetry downloading --- src/packageManager/fileDownloader.ts | 5 +++++ src/razor/src/extension.ts | 13 ++++++++++++- src/razor/src/razorLanguageServerClient.ts | 11 +++-------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/packageManager/fileDownloader.ts b/src/packageManager/fileDownloader.ts index 935e8b4cc..a39919651 100644 --- a/src/packageManager/fileDownloader.ts +++ b/src/packageManager/fileDownloader.ts @@ -66,6 +66,7 @@ async function downloadFile( agent: getProxyAgent(url, proxy, strictSSL), port: url.port, rejectUnauthorized: strictSSL, + timeout: 5 * 60 * 1000, // timeout is in milliseconds }; const buffers: any[] = []; @@ -129,6 +130,10 @@ async function downloadFile( }); }); + request.on('timeout', () => { + request.destroy(new Error(`Timed out trying to download ${urlString}.`)); + }); + request.on('error', (err) => { reject(new NestedError(`Request error: ${err.message || 'NONE'}`, err)); }); diff --git a/src/razor/src/extension.ts b/src/razor/src/extension.ts index 07c5fbde4..ca0dd488e 100644 --- a/src/razor/src/extension.ts +++ b/src/razor/src/extension.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as vscodeapi from 'vscode'; +import * as util from '../../common'; import { ExtensionContext } from 'vscode'; import { BlazorDebugConfigurationProvider } from './blazorDebug/blazorDebugConfigurationProvider'; import { CodeActionsHandler } from './codeActions/codeActionsHandler'; @@ -99,9 +100,19 @@ export async function activate( // Save user's DOTNET_ROOT env-var value so server can recover the user setting when needed env.DOTNET_ROOT_USER = process.env.DOTNET_ROOT ?? 'EMPTY'; + let telemetryExtensionDllPath = ''; // Set up DevKit environment for telemetry if (csharpDevkitExtension) { await setupDevKitEnvironment(env, csharpDevkitExtension, logger); + + const telemetryExtensionPath = path.join( + util.getExtensionPath(), + '.razortelemetry', + 'Microsoft.VisualStudio.DevKit.Razor.dll' + ); + if (await util.fileExists(telemetryExtensionPath)) { + telemetryExtensionDllPath = telemetryExtensionPath; + } } const languageServerClient = new RazorLanguageServerClient( @@ -109,7 +120,7 @@ export async function activate( languageServerDir, razorTelemetryReporter, vscodeTelemetryReporter, - csharpDevkitExtension !== undefined, + telemetryExtensionDllPath, env, dotnetInfo.path, logger diff --git a/src/razor/src/razorLanguageServerClient.ts b/src/razor/src/razorLanguageServerClient.ts index 842a312ca..292dbf817 100644 --- a/src/razor/src/razorLanguageServerClient.ts +++ b/src/razor/src/razorLanguageServerClient.ts @@ -3,10 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; import * as cp from 'child_process'; import { EventEmitter } from 'events'; -import * as util from '../../common'; import * as vscode from 'vscode'; import { RequestHandler, RequestType } from 'vscode-jsonrpc'; import { GenericNotificationHandler, InitializeResult, LanguageClientOptions, State } from 'vscode-languageclient'; @@ -40,7 +38,7 @@ export class RazorLanguageServerClient implements vscode.Disposable { private readonly languageServerDir: string, private readonly razorTelemetryReporter: RazorTelemetryReporter, private readonly vscodeTelemetryReporter: TelemetryReporter, - private readonly isCSharpDevKitActivated: boolean, + private readonly telemetryExtensionDllPath: string, private readonly env: NodeJS.ProcessEnv, private readonly dotnetExecutablePath: string, private readonly logger: RazorLogger @@ -249,13 +247,10 @@ export class RazorLanguageServerClient implements vscode.Disposable { args.push('--UpdateBuffersForClosedDocuments'); args.push('true'); - if (this.isCSharpDevKitActivated) { + if (this.telemetryExtensionDllPath.length > 0) { args.push('--telemetryLevel', this.vscodeTelemetryReporter.telemetryLevel); args.push('--sessionId', getSessionId()); - args.push( - '--telemetryExtensionPath', - path.join(util.getExtensionPath(), '.razortelemetry', 'Microsoft.VisualStudio.DevKit.Razor.dll') - ); + args.push('--telemetryExtensionPath', this.telemetryExtensionDllPath); } } From 3f654aa5e5dd19c3073298329959fa558d6899f4 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 10 Nov 2023 21:39:31 +1100 Subject: [PATCH 2/2] Show a progress bar and allow the user to cancel --- l10n/bundle.l10n.json | 1 + .../downloadAndInstallPackages.ts | 13 ++++++-- src/packageManager/fileDownloader.ts | 20 ++++++----- src/razor/razorTelemetryDownloader.ts | 33 ++++++++++++------- tasks/offlinePackagingTasks.ts | 6 ++-- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 859647485..790179d16 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -36,6 +36,7 @@ "Cancel": "Cancel", "Replace existing build and debug assets?": "Replace existing build and debug assets?", "Unable to generate assets to build and debug. {0}.": "Unable to generate assets to build and debug. {0}.", + "Downloading Razor Telemetry Package": "Downloading Razor Telemetry Package", "Cannot load Razor language server because the directory was not found: '{0}'": "Cannot load Razor language server because the directory was not found: '{0}'", "Could not find '{0}' in or above '{1}'.": "Could not find '{0}' in or above '{1}'.", "Invalid trace setting for Razor language server. Defaulting to '{0}'": "Invalid trace setting for Razor language server. Defaulting to '{0}'", diff --git a/src/packageManager/downloadAndInstallPackages.ts b/src/packageManager/downloadAndInstallPackages.ts index 7a35a0a52..817bd73d7 100644 --- a/src/packageManager/downloadAndInstallPackages.ts +++ b/src/packageManager/downloadAndInstallPackages.ts @@ -15,12 +15,14 @@ import { InstallationFailure, IntegrityCheckFailure } from '../omnisharp/logging import { mkdirpSync } from 'fs-extra'; import { PackageInstallStart } from '../omnisharp/loggingEvents'; import { DownloadValidator } from './isValidDownload'; +import { CancellationToken } from 'vscode'; export async function downloadAndInstallPackages( packages: AbsolutePathPackage[], provider: NetworkSettingsProvider, eventStream: EventStream, - downloadValidator: DownloadValidator + downloadValidator: DownloadValidator, + token?: CancellationToken ): Promise { eventStream.post(new PackageInstallStart()); for (const pkg of packages) { @@ -33,7 +35,14 @@ export async function downloadAndInstallPackages( while (willTryInstallingPackage()) { count = count + 1; installationStage = 'downloadPackage'; - const buffer = await DownloadFile(pkg.description, eventStream, provider, pkg.url, pkg.fallbackUrl); + const buffer = await DownloadFile( + pkg.description, + eventStream, + provider, + pkg.url, + pkg.fallbackUrl, + token + ); if (downloadValidator(buffer, pkg.integrity, eventStream)) { installationStage = 'installPackage'; await InstallZip(buffer, pkg.description, pkg.installPath, pkg.binaries, eventStream); diff --git a/src/packageManager/fileDownloader.ts b/src/packageManager/fileDownloader.ts index a39919651..2dcec511e 100644 --- a/src/packageManager/fileDownloader.ts +++ b/src/packageManager/fileDownloader.ts @@ -17,18 +17,20 @@ import { NestedError } from '../nestedError'; import { parse as parseUrl } from 'url'; import { getProxyAgent } from './proxy'; import { NetworkSettingsProvider } from '../networkSettings'; +import { CancellationToken } from 'vscode'; export async function DownloadFile( description: string, eventStream: EventStream, networkSettingsProvider: NetworkSettingsProvider, url: string, - fallbackUrl?: string + fallbackUrl?: string, + token?: CancellationToken ): Promise { eventStream.post(new DownloadStart(description)); try { - const buffer = await downloadFile(description, url, eventStream, networkSettingsProvider); + const buffer = await downloadFile(description, url, eventStream, networkSettingsProvider, token); eventStream.post(new DownloadSuccess(` Done!`)); return buffer; } catch (primaryUrlError) { @@ -54,7 +56,8 @@ async function downloadFile( description: string, urlString: string, eventStream: EventStream, - networkSettingsProvider: NetworkSettingsProvider + networkSettingsProvider: NetworkSettingsProvider, + token?: CancellationToken ): Promise { const url = parseUrl(urlString); const networkSettings = networkSettingsProvider(); @@ -66,12 +69,15 @@ async function downloadFile( agent: getProxyAgent(url, proxy, strictSSL), port: url.port, rejectUnauthorized: strictSSL, - timeout: 5 * 60 * 1000, // timeout is in milliseconds }; const buffers: any[] = []; return new Promise((resolve, reject) => { + token?.onCancellationRequested(() => { + return reject(new NestedError(`Cancelled downloading ${urlString}.`)); + }); + const request = https.request(options, (response) => { if (response.statusCode === 301 || response.statusCode === 302) { // Redirect - download from new location @@ -82,7 +88,7 @@ async function downloadFile( return reject(new NestedError('Missing location')); } return resolve( - downloadFile(description, response.headers.location, eventStream, networkSettingsProvider) + downloadFile(description, response.headers.location, eventStream, networkSettingsProvider, token) ); } else if (response.statusCode !== 200) { // Download failed - print error message @@ -130,10 +136,6 @@ async function downloadFile( }); }); - request.on('timeout', () => { - request.destroy(new Error(`Timed out trying to download ${urlString}.`)); - }); - request.on('error', (err) => { reject(new NestedError(`Request error: ${err.message || 'NONE'}`, err)); }); diff --git a/src/razor/razorTelemetryDownloader.ts b/src/razor/razorTelemetryDownloader.ts index 4a434ddcc..6d5ea2d20 100644 --- a/src/razor/razorTelemetryDownloader.ts +++ b/src/razor/razorTelemetryDownloader.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; import { PlatformInformation } from '../shared/platform'; import { PackageInstallation, LogPlatformInfo, InstallationSuccess } from '../omnisharp/loggingEvents'; import { EventStream } from '../eventStream'; @@ -42,17 +43,27 @@ export class RazorTelemetryDownloader { if (packagesToInstall.length > 0) { this.eventStream.post(new PackageInstallation(`Razor Telemetry Version = ${version}`)); this.eventStream.post(new LogPlatformInfo(this.platformInfo)); - if ( - await downloadAndInstallPackages( - packagesToInstall, - this.networkSettingsProvider, - this.eventStream, - isValidDownload - ) - ) { - this.eventStream.post(new InstallationSuccess()); - return true; - } + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t('Downloading Razor Telemetry Package'), + cancellable: true, + }, + async (_, token) => { + if ( + await downloadAndInstallPackages( + packagesToInstall, + this.networkSettingsProvider, + this.eventStream, + isValidDownload, + token + ) + ) { + this.eventStream.post(new InstallationSuccess()); + return true; + } + } + ); } return false; diff --git a/tasks/offlinePackagingTasks.ts b/tasks/offlinePackagingTasks.ts index 420f73de5..3fbd5c77f 100644 --- a/tasks/offlinePackagingTasks.ts +++ b/tasks/offlinePackagingTasks.ts @@ -29,6 +29,7 @@ import { getPackageJSON } from '../tasks/packageJson'; import { createPackageAsync } from '../tasks/vsceTasks'; import { isValidDownload } from '../src/packageManager/isValidDownload'; import path = require('path'); +import { CancellationToken } from 'vscode'; // There are no typings for this library. // eslint-disable-next-line @typescript-eslint/no-var-requires const argv = require('yargs').argv; @@ -153,7 +154,8 @@ async function installDebugger(packageJSON: any, platformInfo: PlatformInformati async function installPackageJsonDependency( dependencyName: string, packageJSON: any, - platformInfo: PlatformInformation + platformInfo: PlatformInformation, + token?: CancellationToken ) { const eventStream = new EventStream(); const logger = new Logger((message) => process.stdout.write(message)); @@ -168,7 +170,7 @@ async function installPackageJsonDependency( codeExtensionPath ); const provider = () => new NetworkSettings('', true); - if (!(await downloadAndInstallPackages(packagesToInstall, provider, eventStream, isValidDownload))) { + if (!(await downloadAndInstallPackages(packagesToInstall, provider, eventStream, isValidDownload, token))) { throw Error('Failed to download package.'); } }