diff --git a/src/vs/base/node/request.ts b/src/vs/base/node/request.ts index a43ed2ee8a9..23da75d32ab 100644 --- a/src/vs/base/node/request.ts +++ b/src/vs/base/node/request.ts @@ -14,6 +14,8 @@ import { parse as parseUrl } from 'url'; import { createWriteStream } from 'fs'; import { assign } from 'vs/base/common/objects'; import { createGunzip } from 'zlib'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { canceled } from 'vs/base/common/errors'; export type Agent = any; @@ -46,7 +48,7 @@ export interface IRequestContext { } export interface IRequestFunction { - (options: IRequestOptions): TPromise; + (options: IRequestOptions, token: CancellationToken): TPromise; } async function getNodeRequest(options: IRequestOptions): Promise { @@ -55,7 +57,7 @@ async function getNodeRequest(options: IRequestOptions): Promise { +export function request(options: IRequestOptions, token: CancellationToken): TPromise { let req: http.ClientRequest; const rawRequestPromise = options.getRawRequest @@ -88,7 +90,7 @@ export function request(options: IRequestOptions): TPromise { request(assign({}, options, { url: res.headers['location'], followRedirects: followRedirects - 1 - })).then(c, e); + }), token).then(c, e); } else { let stream: Stream = res; @@ -116,7 +118,12 @@ export function request(options: IRequestOptions): TPromise { } req.end(); - }, () => req && req.abort()); + + token.onCancellationRequested(() => { + req.abort(); + e(canceled()); + }); + }); }); } diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index 581749975cd..38caf8716a4 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -17,6 +17,7 @@ import product from 'vs/platform/node/product'; import { IRequestService } from 'vs/platform/request/node/request'; import { IRequestContext } from 'vs/base/node/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface PostResult { readonly blob_id: string; @@ -86,7 +87,7 @@ async function postLogs( headers: { 'Content-Type': 'application/zip' } - }); + }, CancellationToken.None); } catch (e) { clearInterval(dotter); console.log(localize('postError', 'Error posting logs: {0}', e)); diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 4b9f49f66a5..a78f291c408 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -24,7 +24,6 @@ import { writeFileAndFlushSync } from 'vs/base/node/extfs'; import { generateUuid, isUUID } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { wireCancellationToken } from 'vs/base/common/async'; interface IRawGalleryExtensionFile { assetType: string; @@ -375,7 +374,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withFilter(FilterType.ExtensionName, id); } - return this.queryGallery(query).then(({ galleryExtensions }) => { + return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => { if (galleryExtensions.length) { const galleryExtension = galleryExtensions[0]; const versionAsset = version ? galleryExtension.versions.filter(v => v.version === version)[0] : galleryExtension.versions[0]; @@ -447,7 +446,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withSortOrder(options.sortOrder); } - return this.queryGallery(query).then(({ galleryExtensions, total }) => { + return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions, total }) => { const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source)); const pageSize = query.pageSize; const getPage = (pageIndex: number, ct: CancellationToken) => { @@ -456,17 +455,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const nextPageQuery = query.withPage(pageIndex + 1); - const promise = this.queryGallery(nextPageQuery) + return this.queryGallery(nextPageQuery, ct) .then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source))); - - return wireCancellationToken(ct, promise); }; return { firstPage: extensions, total, pageSize, getPage } as IPager; }); } - private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { + private queryGallery(query: Query, token: CancellationToken): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { return this.commonHeadersPromise.then(commonHeaders => { const data = JSON.stringify(query.raw); const headers = assign({}, commonHeaders, { @@ -481,7 +478,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { url: this.api('/extensionquery'), data, headers - }).then(context => { + }, token).then(context => { if (context.res.statusCode >= 400 && context.res.statusCode < 500) { return { galleryExtensions: [], total: 0 }; @@ -511,7 +508,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { type: 'POST', url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`), headers - }).then(null, () => null); + }, CancellationToken.None).then(null, () => null); }); } @@ -584,7 +581,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { .withAssetTypes(AssetType.Manifest, AssetType.VSIX) .withFilter(FilterType.ExtensionId, extension.identifier.uuid); - return this.queryGallery(query) + return this.queryGallery(query, CancellationToken.None) .then(({ galleryExtensions }) => { const [rawExtension] = galleryExtensions; @@ -619,7 +616,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { .withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX) .withFilter(FilterType.ExtensionName, ...extensionNames); - return this.queryGallery(query).then(result => { + return this.queryGallery(query, CancellationToken.None).then(result => { const dependencies = []; const ids = []; @@ -658,7 +655,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}): TPromise { + private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): TPromise { return this.commonHeadersPromise.then(commonHeaders => { const baseOptions = { type: 'GET' }; const headers = assign({}, commonHeaders, options.headers || {}); @@ -668,7 +665,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const fallbackUrl = asset.fallbackUri; const firstOptions = assign({}, options, { url }); - return this.requestService.request(firstOptions) + return this.requestService.request(firstOptions, token) .then(context => { if (context.res.statusCode === 200) { return TPromise.as(context); @@ -700,7 +697,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { this.telemetryService.publicLog('galleryService:cdnFallback', { url, message }); const fallbackOptions = assign({}, options, { url: fallbackUrl }); - return this.requestService.request(fallbackOptions).then(null, err => { + return this.requestService.request(fallbackOptions, token).then(null, err => { if (isPromiseCanceledError(err)) { return TPromise.wrapError(err); } @@ -783,7 +780,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return TPromise.as([]); } - return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }).then(context => { + return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None).then(context => { if (context.res.statusCode !== 200) { return TPromise.wrapError(new Error('Could not get extensions report.')); } @@ -831,4 +828,4 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic 'X-Market-User-Id': uuid }; }); -} \ No newline at end of file +} diff --git a/src/vs/platform/request/electron-browser/requestService.ts b/src/vs/platform/request/electron-browser/requestService.ts index 272001f9666..c66762f4d49 100644 --- a/src/vs/platform/request/electron-browser/requestService.ts +++ b/src/vs/platform/request/electron-browser/requestService.ts @@ -8,18 +8,20 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IRequestOptions, IRequestContext, IRequestFunction } from 'vs/base/node/request'; import { Readable } from 'stream'; import { RequestService as NodeRequestService } from 'vs/platform/request/node/requestService'; +import { CancellationToken } from 'vscode'; +import { canceled } from 'vs/base/common/errors'; /** * This service exposes the `request` API, while using the global * or configured proxy settings. */ export class RequestService extends NodeRequestService { - request(options: IRequestOptions): TPromise { - return super.request(options, xhrRequest); + request(options: IRequestOptions, token: CancellationToken): TPromise { + return super.request(options, token, xhrRequest); } } -export const xhrRequest: IRequestFunction = (options: IRequestOptions): TPromise => { +export const xhrRequest: IRequestFunction = (options: IRequestOptions, token: CancellationToken): TPromise => { const xhr = new XMLHttpRequest(); return new TPromise((resolve, reject) => { @@ -68,11 +70,12 @@ export const xhrRequest: IRequestFunction = (options: IRequestOptions): TPromise // TODO: remove any xhr.send(options.data as any); - return null; - }, () => { // cancel - xhr.abort(); + token.onCancellationRequested(() => { + xhr.abort(); + reject(canceled()); + }); }); }; diff --git a/src/vs/platform/request/electron-main/requestService.ts b/src/vs/platform/request/electron-main/requestService.ts index 2d583abe0cd..e1b05b56ac6 100644 --- a/src/vs/platform/request/electron-main/requestService.ts +++ b/src/vs/platform/request/electron-main/requestService.ts @@ -9,6 +9,7 @@ import { IRequestOptions, IRequestContext, request, IRawRequestFunction } from ' import { RequestService as NodeRequestService } from 'vs/platform/request/node/requestService'; import { assign } from 'vs/base/common/objects'; import { net } from 'electron'; +import { CancellationToken } from 'vs/base/common/cancellation'; function getRawRequest(options: IRequestOptions): IRawRequestFunction { return net.request as any as IRawRequestFunction; @@ -16,7 +17,7 @@ function getRawRequest(options: IRequestOptions): IRawRequestFunction { export class RequestService extends NodeRequestService { - request(options: IRequestOptions): TPromise { - return super.request(options, options => request(assign({}, options || {}, { getRawRequest }))); + request(options: IRequestOptions, token: CancellationToken): TPromise { + return super.request(options, token, options => request(assign({}, options || {}, { getRawRequest }), token)); } -} \ No newline at end of file +} diff --git a/src/vs/platform/request/node/request.ts b/src/vs/platform/request/node/request.ts index 878a5c4a334..a7bbfc284e5 100644 --- a/src/vs/platform/request/node/request.ts +++ b/src/vs/platform/request/node/request.ts @@ -10,13 +10,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IRequestOptions, IRequestContext } from 'vs/base/node/request'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IRequestService = createDecorator('requestService2'); export interface IRequestService { _serviceBrand: any; - request(options: IRequestOptions): TPromise; + request(options: IRequestOptions, token: CancellationToken): TPromise; } export interface IHTTPConfiguration { @@ -50,4 +51,4 @@ Registry.as(Extensions.Configuration) description: localize('proxyAuthorization', "The value to send as the 'Proxy-Authorization' header for every network request.") } } - }); \ No newline at end of file + }); diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 84a66a3ca52..711f4561315 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -12,6 +12,7 @@ import { getProxyAgent } from 'vs/base/node/proxy'; import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/node/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationToken } from 'vs/base/common/cancellation'; /** * This service exposes the `request` API, while using the global @@ -40,7 +41,7 @@ export class RequestService implements IRequestService { this.authorization = config.http && config.http.proxyAuthorization; } - request(options: IRequestOptions, requestFn: IRequestFunction = request): TPromise { + request(options: IRequestOptions, token: CancellationToken, requestFn: IRequestFunction = request): TPromise { this.logService.trace('RequestService#request', options.url); const { proxyUrl, strictSSL } = this; @@ -54,7 +55,7 @@ export class RequestService implements IRequestService { options.headers = assign(options.headers || {}, { 'Proxy-Authorization': this.authorization }); } - return requestFn(options); + return requestFn(options, token); }); } } diff --git a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts index e5658bcaf38..01335f98f8e 100644 --- a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts @@ -19,6 +19,7 @@ import * as pfs from 'vs/base/node/pfs'; import * as path from 'path'; import * as assert from 'assert'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface ColorInfo { @@ -41,7 +42,7 @@ export const experimental = []; // 'settings.modifiedItemForeground', 'editorUnn suite('Color Registry', function () { test('all colors documented', async function () { - const reqContext = await request({ url: 'https://raw.githubusercontent.com/Microsoft/vscode-docs/vnext/docs/getstarted/theme-color-reference.md' }); + const reqContext = await request({ url: 'https://raw.githubusercontent.com/Microsoft/vscode-docs/vnext/docs/getstarted/theme-color-reference.md' }, CancellationToken.None); const content = await asText(reqContext); const expression = /\-\s*\`([\w\.]+)\`: (.*)/g; diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 18643a2f752..218a14ee8ae 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -15,6 +15,7 @@ import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } fr import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IRequestService } from 'vs/platform/request/node/request'; +import { CancellationToken } from 'vs/base/common/cancellation'; export function createUpdateURL(platform: string, quality: string): string { return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`; @@ -161,7 +162,7 @@ export abstract class AbstractUpdateService implements IUpdateService { if (!this.url) { return TPromise.as(undefined); } - return this.requestService.request({ url: this.url }).then(context => { + return this.requestService.request({ url: this.url }, CancellationToken.None).then(context => { // The update server replies with 204 (No Content) when no // update is available - that's all we want to know. if (context.res.statusCode === 204) { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 87c60c5d436..cce8a725374 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -17,6 +17,7 @@ import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/elect import { asJson } from 'vs/base/node/request'; import { TPromise } from 'vs/base/common/winjs.base'; import { shell } from 'electron'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class LinuxUpdateService extends AbstractUpdateService { @@ -44,7 +45,7 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); - this.requestService.request({ url: this.url }) + this.requestService.request({ url: this.url }, CancellationToken.None) .then(asJson) .then(update => { if (!update || !update.url || !update.version || !update.productVersion) { diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 6f5e750aafa..3d1772cfe97 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -24,6 +24,7 @@ import { checksum } from 'vs/base/node/crypto'; import { tmpdir } from 'os'; import { spawn } from 'child_process'; import { shell } from 'electron'; +import { CancellationToken } from 'vs/base/common/cancellation'; function pollUntil(fn: () => boolean, timeout = 1000): TPromise { return new TPromise(c => { @@ -115,7 +116,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); - this.requestService.request({ url: this.url }) + this.requestService.request({ url: this.url }, CancellationToken.None) .then(asJson) .then(update => { const updateType = getUpdateType(); @@ -150,7 +151,7 @@ export class Win32UpdateService extends AbstractUpdateService { const hash = update.hash; const downloadPath = `${updatePackagePath}.tmp`; - return this.requestService.request({ url }) + return this.requestService.request({ url }, CancellationToken.None) .then(context => download(downloadPath, context)) .then(hash ? () => checksum(downloadPath, update.hash) : () => null) .then(() => pfs.rename(downloadPath, updatePackagePath)) diff --git a/src/vs/workbench/parts/experiments/node/experimentService.ts b/src/vs/workbench/parts/experiments/node/experimentService.ts index 30e8e987ef9..3795466dbe0 100644 --- a/src/vs/workbench/parts/experiments/node/experimentService.ts +++ b/src/vs/workbench/parts/experiments/node/experimentService.ts @@ -21,6 +21,7 @@ import { asJson } from 'vs/base/node/request'; import { Emitter, Event } from 'vs/base/common/event'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; import { WorkspaceStats } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface IExperimentStorageState { enabled: boolean; @@ -168,7 +169,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (!product.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { return TPromise.as([]); } - return this.requestService.request({ type: 'GET', url: product.experimentsUrl }).then(context => { + return this.requestService.request({ type: 'GET', url: product.experimentsUrl }, CancellationToken.None).then(context => { if (context.res.statusCode !== 200) { return TPromise.as(null); } @@ -431,4 +432,4 @@ function safeParse(text: string, defaultObject: any) { catch (e) { return defaultObject; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 67c2a6493f4..89f50e43e36 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -43,6 +43,7 @@ import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { CancellationToken } from 'vs/base/common/cancellation'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -898,7 +899,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return null; } - return this.requestService.request({ type: 'GET', url: this._extensionsRecommendationsUrl }).then(context => { + return this.requestService.request({ type: 'GET', url: this._extensionsRecommendationsUrl }, CancellationToken.None).then(context => { if (context.res.statusCode !== 200) { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts index cd3815509ca..d5c4c523c34 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts @@ -271,7 +271,7 @@ class RemoteSearchProvider implements ISearchProvider { 'api-key': this.options.endpoint.key }, timeout: 5000 - }).then(context => { + }, CancellationToken.None).then(context => { if (context.res.statusCode >= 300) { throw new Error(`${details} returned status code: ${context.res.statusCode}`); } diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index b32b75fb52f..88110f40e35 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -27,6 +27,7 @@ import { IWebviewEditorService } from 'vs/workbench/parts/webview/electron-brows import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; +import { CancellationToken } from 'vs/base/common/cancellation'; function renderBody( body: string, @@ -156,7 +157,7 @@ export class ReleaseNotesManager { }; if (!this._releaseNotesCache[version]) { - this._releaseNotesCache[version] = this._requestService.request({ url }) + this._releaseNotesCache[version] = this._requestService.request({ url }, CancellationToken.None) .then(asText) .then(text => { if (!/^#\s/.test(text)) { // release notes always starts with `#` followed by whitespace