mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 18:22:34 -05:00
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -3,24 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout, Delayer } from 'vs/base/common/async';
|
||||
import { Delayer, disposableTimeout } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
type AutoSyncTriggerClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
type AutoSyncClassification = {
|
||||
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
export const RESOURCE_ENABLEMENT_SOURCE = 'resourceEnablement';
|
||||
|
||||
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private enabled: boolean = false;
|
||||
private readonly autoSync = this._register(new MutableDisposable<AutoSync>());
|
||||
private successiveFailures: number = 0;
|
||||
private readonly syncDelayer: Delayer<void>;
|
||||
private lastSyncTriggerTime: number | undefined = undefined;
|
||||
private readonly syncTriggerDelayer: Delayer<void>;
|
||||
|
||||
private readonly _onError: Emitter<UserDataSyncError> = this._register(new Emitter<UserDataSyncError>());
|
||||
readonly onError: Event<UserDataSyncError> = this._onError.event;
|
||||
@@ -31,100 +36,157 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
this.updateEnablement(false, true);
|
||||
this.syncDelayer = this._register(new Delayer<void>(0));
|
||||
this._register(Event.any<any>(authTokenService.onDidChangeToken)(() => this.updateEnablement(true, true)));
|
||||
this._register(Event.any<any>(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true)));
|
||||
this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false)));
|
||||
this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync(['resourceEnablement'])));
|
||||
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
|
||||
|
||||
if (getUserDataSyncStore(this.productService, this.configurationService)) {
|
||||
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])));
|
||||
}
|
||||
}
|
||||
|
||||
private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise<void> {
|
||||
const { enabled, reason } = await this.isAutoSyncEnabled();
|
||||
if (this.enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = enabled;
|
||||
if (this.enabled) {
|
||||
this.logService.info('Auto Sync: Started');
|
||||
this.sync(true, auto);
|
||||
return;
|
||||
} else {
|
||||
this.resetFailures();
|
||||
if (stopIfDisabled) {
|
||||
this.userDataSyncService.stop();
|
||||
this.logService.info('Auto Sync: stopped because', reason);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async sync(loop: boolean, auto: boolean): Promise<void> {
|
||||
if (this.enabled) {
|
||||
try {
|
||||
await this.userDataSyncService.sync();
|
||||
this.resetFailures();
|
||||
} catch (e) {
|
||||
const error = UserDataSyncError.toUserDataSyncError(e);
|
||||
if (error.code === UserDataSyncErrorCode.TurnedOff || error.code === UserDataSyncErrorCode.SessionExpired) {
|
||||
this.logService.info('Auto Sync: Sync is turned off in the cloud.');
|
||||
this.logService.info('Auto Sync: Resetting the local sync state.');
|
||||
await this.userDataSyncService.resetLocal();
|
||||
this.logService.info('Auto Sync: Completed resetting the local sync state.');
|
||||
if (auto) {
|
||||
this.userDataSyncEnablementService.setEnablement(false);
|
||||
this._onError.fire(error);
|
||||
return;
|
||||
} else {
|
||||
return this.sync(loop, auto);
|
||||
}
|
||||
private updateAutoSync(): void {
|
||||
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.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()) {
|
||||
this.autoSync.value.start();
|
||||
}
|
||||
this.logService.error(error);
|
||||
this.successiveFailures++;
|
||||
this._onError.fire(error);
|
||||
}
|
||||
if (loop) {
|
||||
await timeout(1000 * 60 * 5);
|
||||
this.sync(loop, true);
|
||||
}
|
||||
} else {
|
||||
this.logService.trace('Auto Sync: Not syncing as it is disabled.');
|
||||
if (this.autoSync.value !== undefined) {
|
||||
this.logService.info('Auto Sync: Disabled because', reason);
|
||||
this.autoSync.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async isAutoSyncEnabled(): Promise<{ enabled: boolean, reason?: string }> {
|
||||
// For tests purpose only
|
||||
protected startAutoSync(): boolean { return true; }
|
||||
|
||||
private isAutoSyncEnabled(): { enabled: boolean, reason?: string } {
|
||||
if (!this.userDataSyncEnablementService.isEnabled()) {
|
||||
return { enabled: false, reason: 'sync is disabled' };
|
||||
}
|
||||
if (this.userDataSyncService.status === SyncStatus.Uninitialized) {
|
||||
return { enabled: false, reason: 'sync is not initialized' };
|
||||
}
|
||||
const token = await this.authTokenService.getToken();
|
||||
if (!token) {
|
||||
if (!this.authTokenService.token) {
|
||||
return { enabled: false, reason: 'token is not avaialable' };
|
||||
}
|
||||
return { enabled: true };
|
||||
}
|
||||
|
||||
private resetFailures(): void {
|
||||
this.successiveFailures = 0;
|
||||
private async onDidFinishSync(error: Error | undefined): Promise<void> {
|
||||
if (!error) {
|
||||
// Sync finished without errors
|
||||
this.successiveFailures = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Error while syncing
|
||||
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
|
||||
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);
|
||||
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);
|
||||
this.logService.info('Auto Sync: Turned off sync because of making too many requests to server');
|
||||
} else {
|
||||
this.logService.error(userDataSyncError);
|
||||
this.successiveFailures++;
|
||||
}
|
||||
this._onError.fire(userDataSyncError);
|
||||
}
|
||||
|
||||
private sources: string[] = [];
|
||||
async triggerAutoSync(sources: string[]): Promise<void> {
|
||||
sources.forEach(source => this.telemetryService.publicLog2<{ source: string }, AutoSyncTriggerClassification>('sync/triggerAutoSync', { source }));
|
||||
if (this.enabled) {
|
||||
return this.syncDelayer.trigger(() => {
|
||||
this.logService.info('Auto Sync: Triggered.');
|
||||
return this.sync(false, true);
|
||||
}, this.successiveFailures
|
||||
? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */
|
||||
: 1000);
|
||||
} else {
|
||||
this.syncDelayer.cancel();
|
||||
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
|
||||
&& Math.round((new Date().getTime() - this.lastSyncTriggerTime) / 1000) < 10) {
|
||||
this.logService.debug('Auto Sync Skipped: Limited to once per 10 seconds.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.sources.push(...sources);
|
||||
return this.syncTriggerDelayer.trigger(async () => {
|
||||
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 */
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AutoSync extends Disposable {
|
||||
|
||||
private static readonly INTERVAL_SYNCING = 'Interval';
|
||||
|
||||
private readonly intervalHandler = this._register(new MutableDisposable<IDisposable>());
|
||||
|
||||
private readonly _onDidStartSync = this._register(new Emitter<void>());
|
||||
readonly onDidStartSync = this._onDidStartSync.event;
|
||||
|
||||
private readonly _onDidFinishSync = this._register(new Emitter<Error | undefined>());
|
||||
readonly onDidFinishSync = this._onDidFinishSync.event;
|
||||
|
||||
constructor(
|
||||
private readonly interval: number /* in milliseconds */,
|
||||
private readonly userDataSyncService: IUserDataSyncService,
|
||||
private readonly logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this._register(this.onDidFinishSync(() => this.waitUntilNextIntervalAndSync()));
|
||||
this._register(toDisposable(() => {
|
||||
this.userDataSyncService.stop();
|
||||
this.logService.info('Auto Sync: Stopped');
|
||||
}));
|
||||
this.logService.info('Auto Sync: Started');
|
||||
this.sync(AutoSync.INTERVAL_SYNCING);
|
||||
}
|
||||
|
||||
private waitUntilNextIntervalAndSync(): void {
|
||||
this.intervalHandler.value = disposableTimeout(() => this.sync(AutoSync.INTERVAL_SYNCING), this.interval);
|
||||
}
|
||||
|
||||
async sync(reason: string): Promise<void> {
|
||||
this.logService.info(`Auto Sync: Triggered by ${reason}`);
|
||||
this._onDidStartSync.fire();
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
await this.userDataSyncService.sync();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
error = e;
|
||||
}
|
||||
this._onDidFinishSync.fire(error);
|
||||
}
|
||||
|
||||
register<T extends IDisposable>(t: T): T {
|
||||
return super._register(t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user