Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae

This commit is contained in:
ADS Merger
2020-06-18 04:32:54 +00:00
committed by AzureDataStudio
parent a971aee5bd
commit 5e7071e466
1002 changed files with 24201 additions and 13193 deletions

View File

@@ -3,22 +3,73 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Delayer, disposableTimeout } from 'vs/base/common/async';
import { Delayer, disposableTimeout, CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, toDisposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService, ALL_SYNC_RESOURCES, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, UserDataAutoSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { localize } from 'vs/nls';
type AutoSyncClassification = {
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
export const RESOURCE_ENABLEMENT_SOURCE = 'resourceEnablement';
type AutoSyncEnablementClassification = {
enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
type AutoSyncErrorClassification = {
code: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
const enablementKey = 'sync.enable';
const disableMachineEventuallyKey = 'sync.disableMachineEventually';
const sessionIdKey = 'sync.sessionId';
export class UserDataAutoSyncEnablementService extends Disposable {
private _onDidChangeEnablement = new Emitter<boolean>();
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
constructor(
@IStorageService protected readonly storageService: IStorageService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super();
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
}
isEnabled(): boolean {
switch (this.environmentService.sync) {
case 'on':
return true;
case 'off':
return false;
}
return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, this.environmentService.enableSyncByDefault);
}
canToggleEnablement(): boolean {
return this.environmentService.sync === undefined;
}
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
if (enablementKey === workspaceStorageChangeEvent.key) {
this._onDidChangeEnablement.fire(this.isEnabled());
}
}
}
}
export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService implements IUserDataAutoSyncService {
_serviceBrand: any;
@@ -31,21 +82,27 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
readonly onError: Event<UserDataSyncError> = this._onError.event;
constructor(
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IProductService private readonly productService: IProductService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
@IStorageService storageService: IStorageService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super();
super(storageService, environmentService);
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
if (getUserDataSyncStore(this.productService, this.configurationService)) {
if (userDataSyncStoreService.userDataSyncStore) {
this.updateAutoSync();
this._register(Event.any(authTokenService.onDidChangeToken, this.userDataSyncEnablementService.onDidChangeEnablement)(() => this.updateAutoSync()));
this._register(Event.filter(this.userDataSyncEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerAutoSync([RESOURCE_ENABLEMENT_SOURCE])));
if (this.hasToDisableMachineEventually()) {
this.disableMachineEventually();
}
this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync()));
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));
this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false)));
}
}
@@ -53,7 +110,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
const { enabled, reason } = this.isAutoSyncEnabled();
if (enabled) {
if (this.autoSync.value === undefined) {
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncService, this.logService);
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService);
this.autoSync.value.register(this.autoSync.value.onDidStartSync(() => this.lastSyncTriggerTime = new Date().getTime()));
this.autoSync.value.register(this.autoSync.value.onDidFinishSync(e => this.onDidFinishSync(e)));
if (this.startAutoSync()) {
@@ -61,6 +118,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
}
}
} else {
this.syncTriggerDelayer.cancel();
if (this.autoSync.value !== undefined) {
this.logService.info('Auto Sync: Disabled because', reason);
this.autoSync.clear();
@@ -72,15 +130,66 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
protected startAutoSync(): boolean { return true; }
private isAutoSyncEnabled(): { enabled: boolean, reason?: string } {
if (!this.userDataSyncEnablementService.isEnabled()) {
if (!this.isEnabled()) {
return { enabled: false, reason: 'sync is disabled' };
}
if (!this.authTokenService.token) {
if (!this.userDataSyncAccountService.account) {
return { enabled: false, reason: 'token is not avaialable' };
}
return { enabled: true };
}
async turnOn(pullFirst: boolean): Promise<void> {
this.stopDisableMachineEventually();
if (pullFirst) {
await this.userDataSyncService.pull();
} else {
await this.userDataSyncService.sync();
}
this.setEnablement(true);
}
async turnOff(everywhere: boolean, softTurnOffOnError?: boolean, donotRemoveMachine?: boolean): Promise<void> {
try {
// Remove machine
if (!donotRemoveMachine) {
await this.userDataSyncMachinesService.removeCurrentMachine();
}
// Disable Auto Sync
this.setEnablement(false);
// Reset Session
this.storageService.remove(sessionIdKey, StorageScope.GLOBAL);
// Reset
if (everywhere) {
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
await this.userDataSyncService.reset();
} else {
await this.userDataSyncService.resetLocal();
}
} catch (error) {
if (softTurnOffOnError) {
this.logService.error(error);
this.setEnablement(false);
} else {
throw error;
}
}
}
private setEnablement(enabled: boolean): void {
if (this.isEnabled() !== enabled) {
this.telemetryService.publicLog2<{ enabled: boolean }, AutoSyncEnablementClassification>(enablementKey, { enabled });
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
this.updateAutoSync();
}
}
private async onDidFinishSync(error: Error | undefined): Promise<void> {
if (!error) {
// Sync finished without errors
@@ -90,52 +199,89 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
// Error while syncing
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
// Log to telemetry
if (userDataSyncError instanceof UserDataAutoSyncError) {
this.telemetryService.publicLog2<{ code: string }, AutoSyncErrorClassification>(`autosync/error`, { code: userDataSyncError.code });
}
// Turned off from another device or session got expired
if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) {
this.logService.info('Auto Sync: Sync is turned off in the cloud.');
await this.userDataSyncService.resetLocal();
this.logService.info('Auto Sync: Did reset the local sync state.');
this.userDataSyncEnablementService.setEnablement(false);
await this.turnOff(false, true /* force soft turnoff on error */);
this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud');
} else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests) {
this.userDataSyncEnablementService.setEnablement(false);
}
// Exceeded Rate Limit
else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests || userDataSyncError.code === UserDataSyncErrorCode.TooManyRequests) {
await this.turnOff(false, true /* force soft turnoff on error */,
true /* do not disable machine because disabling a machine makes request to server and can fail with TooManyRequests */);
this.disableMachineEventually();
this.logService.info('Auto Sync: Turned off sync because of making too many requests to server');
} else {
}
else {
this.logService.error(userDataSyncError);
this.successiveFailures++;
}
this._onError.fire(userDataSyncError);
}
private async disableMachineEventually(): Promise<void> {
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL);
await timeout(1000 * 60 * 10);
// Return if got stopped meanwhile.
if (!this.hasToDisableMachineEventually()) {
return;
}
this.stopDisableMachineEventually();
// disable only if sync is disabled
if (!this.isEnabled() && this.userDataSyncAccountService.account) {
await this.userDataSyncMachinesService.removeCurrentMachine();
}
}
private hasToDisableMachineEventually(): boolean {
return this.storageService.getBoolean(disableMachineEventuallyKey, StorageScope.GLOBAL, false);
}
private stopDisableMachineEventually(): void {
this.storageService.remove(disableMachineEventuallyKey, StorageScope.GLOBAL);
}
private sources: string[] = [];
async triggerAutoSync(sources: string[]): Promise<void> {
async triggerSync(sources: string[], skipIfSyncedRecently: boolean): Promise<void> {
if (this.autoSync.value === undefined) {
return this.syncTriggerDelayer.cancel();
}
/*
If sync is not triggered by sync resource (triggered by other sources like window focus etc.,) or by resource enablement
then limit sync to once per 10s
*/
const hasToLimitSync = sources.indexOf(RESOURCE_ENABLEMENT_SOURCE) === -1 && ALL_SYNC_RESOURCES.every(syncResource => sources.indexOf(syncResource) === -1);
if (hasToLimitSync && this.lastSyncTriggerTime
if (skipIfSyncedRecently && this.lastSyncTriggerTime
&& Math.round((new Date().getTime() - this.lastSyncTriggerTime) / 1000) < 10) {
this.logService.debug('Auto Sync Skipped: Limited to once per 10 seconds.');
this.logService.debug('Auto Sync: Skipped. Limited to once per 10 seconds.');
return;
}
this.sources.push(...sources);
return this.syncTriggerDelayer.trigger(async () => {
this.logService.trace('activity sources', ...this.sources);
this.telemetryService.publicLog2<{ sources: string[] }, AutoSyncClassification>('sync/triggered', { sources: this.sources });
this.sources = [];
if (this.autoSync.value) {
await this.autoSync.value.sync('Activity');
}
}, this.successiveFailures
? 1000 * 1 * Math.min(Math.pow(2, this.successiveFailures), 60) /* Delay exponentially until max 1 minute */
: 1000); /* Debounce for a second if there are no failures */
? this.getSyncTriggerDelayTime() * 1 * Math.min(Math.pow(2, this.successiveFailures), 60) /* Delay exponentially until max 1 minute */
: this.getSyncTriggerDelayTime());
}
protected getSyncTriggerDelayTime(): number {
return 1000; /* Debounce for a second if there are no failures */
}
}
class AutoSync extends Disposable {
@@ -150,10 +296,15 @@ class AutoSync extends Disposable {
private readonly _onDidFinishSync = this._register(new Emitter<Error | undefined>());
readonly onDidFinishSync = this._onDidFinishSync.event;
private syncPromise: CancelablePromise<void> | undefined;
constructor(
private readonly interval: number /* in milliseconds */,
private readonly userDataSyncStoreService: IUserDataSyncStoreService,
private readonly userDataSyncService: IUserDataSyncService,
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
private readonly logService: IUserDataSyncLogService,
private readonly storageService: IStorageService,
) {
super();
}
@@ -161,6 +312,11 @@ class AutoSync extends Disposable {
start(): void {
this._register(this.onDidFinishSync(() => this.waitUntilNextIntervalAndSync()));
this._register(toDisposable(() => {
if (this.syncPromise) {
this.syncPromise.cancel();
this.logService.info('Auto sync: Canelled sync that is in progress');
this.syncPromise = undefined;
}
this.userDataSyncService.stop();
this.logService.info('Auto Sync: Stopped');
}));
@@ -172,16 +328,87 @@ class AutoSync extends Disposable {
this.intervalHandler.value = disposableTimeout(() => this.sync(AutoSync.INTERVAL_SYNCING), this.interval);
}
async sync(reason: string): Promise<void> {
sync(reason: string): Promise<void> {
const syncPromise = createCancelablePromise(async token => {
if (this.syncPromise) {
try {
// Wait until existing sync is finished
this.logService.debug('Auto Sync: Waiting until sync is finished.');
await this.syncPromise;
} catch (error) {
if (isPromiseCanceledError(error)) {
// Cancelled => Disposed. Donot continue sync.
return;
}
}
}
return this.doSync(reason, token);
});
this.syncPromise = syncPromise;
this.syncPromise.finally(() => this.syncPromise = undefined);
return this.syncPromise;
}
private async doSync(reason: string, token: CancellationToken): Promise<void> {
this.logService.info(`Auto Sync: Triggered by ${reason}`);
this._onDidStartSync.fire();
let error: Error | undefined;
try {
await this.userDataSyncService.sync();
const syncTask = await this.userDataSyncService.createSyncTask();
let manifest = syncTask.manifest;
// Server has no data but this machine was synced before
if (manifest === null && await this.userDataSyncService.hasPreviouslySynced()) {
// Sync was turned off in the cloud
throw new UserDataAutoSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
}
const sessionId = this.storageService.get(sessionIdKey, StorageScope.GLOBAL);
// Server session is different from client session
if (sessionId && manifest && sessionId !== manifest.session) {
throw new UserDataAutoSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
}
const machines = await this.userDataSyncMachinesService.getMachines(manifest || undefined);
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
const currentMachine = machines.find(machine => machine.isCurrent);
// Check if sync was turned off from other machine
if (currentMachine?.disabled) {
// Throw TurnedOff error
throw new UserDataAutoSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
}
await syncTask.run(token);
// After syncing, get the manifest if it was not available before
if (manifest === null) {
manifest = await this.userDataSyncStoreService.manifest();
}
// Update local session id
if (manifest && manifest.session !== sessionId) {
this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL);
}
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
// Add current machine
if (!currentMachine) {
await this.userDataSyncMachinesService.addCurrentMachine(manifest || undefined);
}
} catch (e) {
this.logService.error(e);
error = e;
}
this._onDidFinishSync.fire(error);
}