diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 5ab5b3dd21d52..685053d0186aa 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -17,7 +17,7 @@ import { localize } from '../../../nls.js'; import { IProductService } from '../../product/common/productService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; -import { IUserDataSyncTask, IUserDataAutoSyncService, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, UserDataAutoSyncError, UserDataSyncError, UserDataSyncErrorCode } from './userDataSync.js'; +import { IUserDataSyncTask, IUserDataAutoSyncService, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, UserDataAutoSyncError, UserDataSyncError, UserDataSyncErrorCode, SyncOptions } from './userDataSync.js'; import { IUserDataSyncAccountService } from './userDataSyncAccount.js'; import { IUserDataSyncMachinesService } from './userDataSyncMachines.js'; @@ -87,21 +87,21 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto if (this.syncUrl) { - this.logService.info('Using settings sync service', this.syncUrl.toString()); + this.logService.info('[AutoSync] Using settings sync service', this.syncUrl.toString()); this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => { if (!isEqual(this.syncUrl, userDataSyncStoreManagementService.userDataSyncStore?.url)) { this.lastSyncUrl = this.syncUrl; this.syncUrl = userDataSyncStoreManagementService.userDataSyncStore?.url; if (this.syncUrl) { - this.logService.info('Using settings sync service', this.syncUrl.toString()); + this.logService.info('[AutoSync] Using settings sync service', this.syncUrl.toString()); } } })); if (this.userDataSyncEnablementService.isEnabled()) { - this.logService.info('Auto Sync is enabled.'); + this.logService.info('[AutoSync] Enabled.'); } else { - this.logService.info('Auto Sync is disabled.'); + this.logService.info('[AutoSync] Disabled.'); } this.updateAutoSync(); @@ -111,9 +111,9 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync())); this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync())); - this._register(userDataSyncService.onDidChangeLocal(source => this.triggerSync([source], false, false))); - this._register(Event.filter(this.userDataSyncEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false, false))); - this._register(this.userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.triggerSync(['userDataSyncStoreChanged'], false, false))); + this._register(userDataSyncService.onDidChangeLocal(source => this.triggerSync([source]))); + this._register(Event.filter(this.userDataSyncEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement']))); + this._register(this.userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.triggerSync(['userDataSyncStoreChanged']))); } } @@ -149,16 +149,16 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private isAutoSyncEnabled(): { enabled: boolean; message?: string } { if (!this.userDataSyncEnablementService.isEnabled()) { - return { enabled: false, message: 'Auto Sync: Disabled.' }; + return { enabled: false, message: '[AutoSync] Disabled.' }; } if (!this.userDataSyncAccountService.account) { - return { enabled: false, message: 'Auto Sync: Suspended until auth token is available.' }; + return { enabled: false, message: '[AutoSync] Suspended until auth token is available.' }; } if (this.userDataSyncStoreService.donotMakeRequestsUntil) { - return { enabled: false, message: `Auto Sync: Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` }; + return { enabled: false, message: `[AutoSync] Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` }; } if (this.suspendUntilRestart) { - return { enabled: false, message: 'Auto Sync: Suspended until restart.' }; + return { enabled: false, message: '[AutoSync] Suspended until restart.' }; } return { enabled: true }; } @@ -211,6 +211,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } private async onDidFinishSync(error: Error | undefined): Promise { + this.logService.debug('[AutoSync] Sync Finished'); if (!error) { // Sync finished without errors this.successiveFailures = 0; @@ -223,19 +224,19 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto // Session got expired if (userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) { await this.turnOff(false, true /* force soft turnoff on error */); - this.logService.info('Auto Sync: Turned off sync because current session is expired'); + this.logService.info('[AutoSync] Turned off sync because current session is expired'); } // Turned off from another device else if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff) { 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'); + this.logService.info('[AutoSync] Turned off sync because sync is turned off in the cloud'); } // Exceeded Rate Limit on Client else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests) { this.suspendUntilRestart = true; - this.logService.info('Auto Sync: Suspended sync because of making too many requests to server'); + this.logService.info('[AutoSync] Suspended sync because of making too many requests to server'); this.updateAutoSync(); } @@ -244,13 +245,13 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto 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'); + this.logService.info('[AutoSync] Turned off sync because of making too many requests to server'); } // Method Not Found else if (userDataSyncError.code === UserDataSyncErrorCode.MethodNotFound) { await this.turnOff(false, true /* force soft turnoff on error */); - this.logService.info('Auto Sync: Turned off sync because current client is making requests to server that are not supported'); + this.logService.info('[AutoSync] Turned off sync because current client is making requests to server that are not supported'); } // Upgrade Required or Gone @@ -258,19 +259,19 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto 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 upgrade required or gone */); this.disableMachineEventually(); - this.logService.info('Auto Sync: Turned off sync because current client is not compatible with server. Requires client upgrade.'); + this.logService.info('[AutoSync] Turned off sync because current client is not compatible with server. Requires client upgrade.'); } // Incompatible Local Content else if (userDataSyncError.code === UserDataSyncErrorCode.IncompatibleLocalContent) { await this.turnOff(false, true /* force soft turnoff on error */); - this.logService.info(`Auto Sync: Turned off sync because server has ${userDataSyncError.resource} content with newer version than of client. Requires client upgrade.`); + this.logService.info(`[AutoSync] Turned off sync because server has ${userDataSyncError.resource} content with newer version than of client. Requires client upgrade.`); } // Incompatible Remote Content else if (userDataSyncError.code === UserDataSyncErrorCode.IncompatibleRemoteContent) { await this.turnOff(false, true /* force soft turnoff on error */); - this.logService.info(`Auto Sync: Turned off sync because server has ${userDataSyncError.resource} content with older version than of client. Requires server reset.`); + this.logService.info(`[AutoSync] Turned off sync because server has ${userDataSyncError.resource} content with older version than of client. Requires server reset.`); } // Service changed @@ -280,7 +281,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto // Then turn off settings sync and ask user to turn on again if (isWeb && userDataSyncError.code === UserDataSyncErrorCode.DefaultServiceChanged && !this.hasProductQualityChanged()) { await this.turnOff(false, true /* force soft turnoff on error */); - this.logService.info('Auto Sync: Turned off sync because default sync service is changed.'); + this.logService.info('[AutoSync] Turned off sync because default sync service is changed.'); } // Service has changed by the user. So turn off and turn on sync. @@ -288,7 +289,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto else { await this.turnOff(false, true /* force soft turnoff on error */, true /* do not disable machine */); await this.turnOn(); - this.logService.info('Auto Sync: Sync Service changed. Turned off auto sync, reset local state and turned on auto sync.'); + this.logService.info('[AutoSync] Sync Service changed. Turned off auto sync, reset local state and turned on auto sync.'); } } @@ -327,32 +328,35 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } private sources: string[] = []; - async triggerSync(sources: string[], skipIfSyncedRecently: boolean, disableCache: boolean): Promise { + async triggerSync(sources: string[], options?: SyncOptions): Promise { if (this.autoSync.value === undefined) { return this.syncTriggerDelayer.cancel(); } - 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.'); + if (options?.skipIfSyncedRecently && this.lastSyncTriggerTime && new Date().getTime() - this.lastSyncTriggerTime < 15_000) { + this.logService.debug('[AutoSync] Skipping because sync was triggered recently.', sources); return; } this.sources.push(...sources); return this.syncTriggerDelayer.trigger(async () => { - this.logService.trace('activity sources', ...this.sources); + this.logService.trace('[AutoSync] Activity sources', ...this.sources); this.sources = []; if (this.autoSync.value) { - await this.autoSync.value.sync('Activity', disableCache); + await this.autoSync.value.sync('Activity', !!options?.disableCache); } }, this.successiveFailures - ? this.getSyncTriggerDelayTime() * 1 * Math.min(Math.pow(2, this.successiveFailures), 60) /* Delay exponentially until max 1 minute */ - : this.getSyncTriggerDelayTime()); + ? Math.min(this.getSyncTriggerDelayTime() * this.successiveFailures, 60_000) /* Delay linearly until max 1 minute */ + : options?.immediately ? 0 : this.getSyncTriggerDelayTime()); } protected getSyncTriggerDelayTime(): number { - return 2000; /* Debounce for 2 seconds if there are no failures */ + if (this.lastSyncTriggerTime && new Date().getTime() - this.lastSyncTriggerTime > 15_000) { + this.logService.debug('[AutoSync] Sync immediately because last sync was triggered more than 15 seconds ago.'); + return 0; + } + return 10_000; /* Debounce for 10 seconds if there are no failures */ } } @@ -392,11 +396,11 @@ class AutoSync extends Disposable { this._register(toDisposable(() => { if (this.syncPromise) { this.syncPromise.cancel(); - this.logService.info('Auto sync: Cancelled sync that is in progress'); + this.logService.info('[AutoSync] Cancelled sync that is in progress'); this.syncPromise = undefined; } this.syncTask?.stop(); - this.logService.info('Auto Sync: Stopped'); + this.logService.info('[AutoSync] Stopped'); })); this.sync(AutoSync.INTERVAL_SYNCING, false); } @@ -413,7 +417,7 @@ class AutoSync extends Disposable { if (this.syncPromise) { try { // Wait until existing sync is finished - this.logService.debug('Auto Sync: Waiting until sync is finished.'); + this.logService.debug('[AutoSync] Waiting until sync is finished.'); await this.syncPromise; } catch (error) { if (isCancellationError(error)) { @@ -444,7 +448,7 @@ class AutoSync extends Disposable { } private async doSync(reason: string, disableCache: boolean, token: CancellationToken): Promise { - this.logService.info(`Auto Sync: Triggered by ${reason}`); + this.logService.info(`[AutoSync] Triggered by ${reason}`); this._onDidStartSync.fire(); let error: Error | undefined; @@ -455,9 +459,9 @@ class AutoSync extends Disposable { error = e; if (UserDataSyncError.toUserDataSyncError(e).code === UserDataSyncErrorCode.MethodNotFound) { try { - this.logService.info('Auto Sync: Client is making invalid requests. Cleaning up data...'); + this.logService.info('[AutoSync] Client is making invalid requests. Cleaning up data...'); await this.userDataSyncService.cleanUpRemoteData(); - this.logService.info('Auto Sync: Retrying sync...'); + this.logService.info('[AutoSync] Retrying sync...'); await this.createAndRunSyncTask(disableCache, token); error = undefined; } catch (e1) { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 02824fa34676a..1a231715b5109 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -601,13 +601,15 @@ export interface IUserDataSyncResourceProviderService { resolveUserDataSyncResource(syncResourceHandle: ISyncResourceHandle): IUserDataSyncResource | undefined; } +export type SyncOptions = { immediately?: boolean; skipIfSyncedRecently?: boolean; disableCache?: boolean }; + export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; readonly onError: Event; turnOn(): Promise; turnOff(everywhere: boolean): Promise; - triggerSync(sources: string[], hasToLimitSync: boolean, disableCache: boolean): Promise; + triggerSync(sources: string[], options?: SyncOptions): Promise; } export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); diff --git a/src/vs/platform/userDataSync/node/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/node/userDataAutoSyncService.ts index f8642d424a42c..821040b4bcfbe 100644 --- a/src/vs/platform/userDataSync/node/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/node/userDataAutoSyncService.ts @@ -33,7 +33,7 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { this._register(Event.debounce(Event.any( Event.map(nativeHostService.onDidFocusMainWindow, () => 'windowFocus'), Event.map(nativeHostService.onDidOpenMainWindow, () => 'windowOpen'), - ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, true, false))); + ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, { skipIfSyncedRecently: true }))); } } diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 9c3164a97b7ca..07362eacb0248 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -22,7 +22,7 @@ class TestUserDataAutoSyncService extends UserDataAutoSyncService { protected override getSyncTriggerDelayTime(): number { return 50; } sync(): Promise { - return this.triggerSync(['sync'], false, false); + return this.triggerSync(['sync']); } } @@ -44,7 +44,7 @@ suite('UserDataAutoSyncService', () => { const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with settings change - await testObject.triggerSync([SyncResource.Settings], false, false); + await testObject.triggerSync([SyncResource.Settings]); // Filter out machine requests const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); @@ -69,7 +69,7 @@ suite('UserDataAutoSyncService', () => { // Trigger auto sync with settings change multiple times for (let counter = 0; counter < 2; counter++) { - await testObject.triggerSync([SyncResource.Settings], false, false); + await testObject.triggerSync([SyncResource.Settings]); } // Filter out machine requests @@ -96,7 +96,7 @@ suite('UserDataAutoSyncService', () => { const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); // Trigger auto sync with window focus once - await testObject.triggerSync(['windowFocus'], true, false); + await testObject.triggerSync(['windowFocus']); // Filter out machine requests const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); @@ -121,7 +121,7 @@ suite('UserDataAutoSyncService', () => { // Trigger auto sync with window focus multiple times for (let counter = 0; counter < 2; counter++) { - await testObject.triggerSync(['windowFocus'], true, false); + await testObject.triggerSync(['windowFocus'], { skipIfSyncedRecently: true }); } // Filter out machine requests @@ -440,7 +440,7 @@ suite('UserDataAutoSyncService', () => { await testClient.setUp(); const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.triggerSync(['some reason'], true, true); + await testObject.triggerSync(['some reason'], { disableCache: true }); assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); }); }); @@ -454,7 +454,7 @@ suite('UserDataAutoSyncService', () => { await testClient.setUp(); const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.triggerSync(['some reason'], true, false); + await testObject.triggerSync(['some reason']); assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); }); }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 4d1acfc51d532..ecb4f7da5738d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2674,7 +2674,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } else { this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, !isIgnored); } - await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated'], false, false); + await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated']); } async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts index 0e7f57554aee9..b775cd53968d7 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -39,9 +39,9 @@ export class UserDataSyncTrigger extends Disposable implements IWorkbenchContrib Event.map(hostService.onDidChangeFocus, () => 'windowFocus'), Event.map(event, source => source!), ), (last, source) => last ? [...last, source] : [source], 1000) - (sources => userDataAutoSyncService.triggerSync(sources, true, false))); + (sources => userDataAutoSyncService.triggerSync(sources, { skipIfSyncedRecently: true }))); } else { - this._register(event(source => userDataAutoSyncService.triggerSync([source!], true, false))); + this._register(event(source => userDataAutoSyncService.triggerSync([source!], { skipIfSyncedRecently: true }))); } } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index ea27f81b3bdf1..b80dbf6c2b067 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -365,7 +365,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } syncNow(): Promise { - return this.userDataAutoSyncService.triggerSync(['Sync Now'], false, true); + return this.userDataAutoSyncService.triggerSync(['Sync Now'], { immediately: true, disableCache: true }); } private async doTurnOnSync(token: CancellationToken): Promise { diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts index 57065b450eee1..5c265c0ee654d 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService, UserDataSyncError } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { IUserDataAutoSyncService, SyncOptions, UserDataSyncError } from '../../../../platform/userDataSync/common/userDataSync.js'; import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; import { IChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { Event } from '../../../../base/common/event.js'; @@ -22,8 +22,8 @@ class UserDataAutoSyncService implements IUserDataAutoSyncService { this.channel = sharedProcessService.getChannel('userDataAutoSync'); } - triggerSync(sources: string[], hasToLimitSync: boolean, disableCache: boolean): Promise { - return this.channel.call('triggerSync', [sources, hasToLimitSync, disableCache]); + triggerSync(sources: string[], options?: SyncOptions): Promise { + return this.channel.call('triggerSync', [sources, options]); } turnOn(): Promise {