提交 ac012cb8 编写于 作者: S Sandeep Somavarapu

Fix #89750

上级 e5dadf5e
...@@ -72,6 +72,13 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto ...@@ -72,6 +72,13 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
return this.sync(loop, auto); return this.sync(loop, auto);
} }
} }
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) {
this.logService.info('Auto Sync: Cloud has new session');
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.');
return this.sync(loop, auto);
}
this.logService.error(e); this.logService.error(e);
this.successiveFailures++; this.successiveFailures++;
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown });
......
...@@ -166,6 +166,7 @@ export enum UserDataSyncErrorCode { ...@@ -166,6 +166,7 @@ export enum UserDataSyncErrorCode {
TooLarge = 'TooLarge', TooLarge = 'TooLarge',
NoRef = 'NoRef', NoRef = 'NoRef',
TurnedOff = 'TurnedOff', TurnedOff = 'TurnedOff',
SessionExpired = 'SessionExpired',
// Local Errors // Local Errors
LocalPreconditionFailed = 'LocalPreconditionFailed', LocalPreconditionFailed = 'LocalPreconditionFailed',
......
...@@ -14,11 +14,14 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; ...@@ -14,11 +14,14 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { equals } from 'vs/base/common/arrays'; import { equals } from 'vs/base/common/arrays';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
type SyncErrorClassification = { type SyncErrorClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
}; };
const SESSION_ID_KEY = 'sync.sessionId';
export class UserDataSyncService extends Disposable implements IUserDataSyncService { export class UserDataSyncService extends Disposable implements IUserDataSyncService {
_serviceBrand: any; _serviceBrand: any;
...@@ -48,6 +51,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ...@@ -48,6 +51,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
@ITelemetryService private readonly telemetryService: ITelemetryService, @ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
) { ) {
super(); super();
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
...@@ -96,7 +100,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ...@@ -96,7 +100,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
} }
const manifest = await this.userDataSyncStoreService.manifest(); let manifest = await this.userDataSyncStoreService.manifest();
// Server has no data but this machine was synced before // Server has no data but this machine was synced before
if (manifest === null && await this.hasPreviouslySynced()) { if (manifest === null && await this.hasPreviouslySynced()) {
...@@ -104,6 +108,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ...@@ -104,6 +108,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff); throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
} }
const sessionId = this.storageService.get(SESSION_ID_KEY, StorageScope.GLOBAL);
// Server session is different from client session
if (sessionId && manifest && sessionId !== manifest.session) {
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
}
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
try { try {
await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined);
...@@ -112,6 +122,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ...@@ -112,6 +122,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
} }
} }
// 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(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
}
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
} finally { } finally {
...@@ -169,6 +189,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ...@@ -169,6 +189,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async resetLocal(): Promise<void> { async resetLocal(): Promise<void> {
await this.checkEnablement(); await this.checkEnablement();
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
try { try {
synchroniser.resetLocal(); synchroniser.resetLocal();
......
...@@ -44,7 +44,7 @@ export class UserDataSyncClient extends Disposable { ...@@ -44,7 +44,7 @@ export class UserDataSyncClient extends Disposable {
this.instantiationService = new TestInstantiationService(); this.instantiationService = new TestInstantiationService();
} }
async setUp(): Promise<void> { async setUp(empty: boolean = false): Promise<void> {
const userDataDirectory = URI.file('userdata').with({ scheme: Schemas.inMemory }); const userDataDirectory = URI.file('userdata').with({ scheme: Schemas.inMemory });
const userDataSyncHome = joinPath(userDataDirectory, '.sync'); const userDataSyncHome = joinPath(userDataDirectory, '.sync');
const environmentService = this.instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{ const environmentService = this.instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{
...@@ -71,7 +71,6 @@ export class UserDataSyncClient extends Disposable { ...@@ -71,7 +71,6 @@ export class UserDataSyncClient extends Disposable {
authenticationProviderId: 'test' authenticationProviderId: 'test'
} }
}))); })));
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([])));
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
await configurationService.initialize(); await configurationService.initialize();
...@@ -102,6 +101,14 @@ export class UserDataSyncClient extends Disposable { ...@@ -102,6 +101,14 @@ export class UserDataSyncClient extends Disposable {
this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser)); this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser));
this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService));
if (empty) {
await fileService.del(environmentService.settingsResource);
} else {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([])));
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' })));
}
await configurationService.reloadConfiguration();
} }
} }
......
...@@ -41,7 +41,38 @@ suite('UserDataSyncService', () => { ...@@ -41,7 +41,38 @@ suite('UserDataSyncService', () => {
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions // Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } } { type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
});
test('test first time sync ever with no data', async () => {
// Setup the client
const target = new UserDataSyncTestServer();
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp(true);
const testObject = client.instantiationService.get(IUserDataSyncService);
// Sync for first time
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Settings
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
// Keybindings
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
// Global state
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]); ]);
}); });
...@@ -333,12 +364,14 @@ suite('UserDataSyncService', () => { ...@@ -333,12 +364,14 @@ suite('UserDataSyncService', () => {
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions // Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } } { type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]); ]);
}); });
test('test delete on one client throws error on other client while syncing', async () => { test('test delete on one client throws turned off error on other client while syncing', async () => {
const target = new UserDataSyncTestServer(); const target = new UserDataSyncTestServer();
// Set up and sync from the client // Set up and sync from the client
...@@ -371,6 +404,42 @@ suite('UserDataSyncService', () => { ...@@ -371,6 +404,42 @@ suite('UserDataSyncService', () => {
throw assert.fail('Should fail with turned off error'); throw assert.fail('Should fail with turned off error');
}); });
test('test creating new session from one client throws session expired error on another client while syncing', async () => {
const target = new UserDataSyncTestServer();
// Set up and sync from the client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync();
// Set up and sync from the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync();
// Reset from the first client
await client.instantiationService.get(IUserDataSyncService).reset();
// Sync again from the first client to create new session
await client.instantiationService.get(IUserDataSyncService).sync();
// Sync from the test client
target.reset();
try {
await testObject.sync();
} catch (e) {
assert.ok(e instanceof UserDataSyncError);
assert.deepEqual((<UserDataSyncError>e).code, UserDataSyncErrorCode.SessionExpired);
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
return;
}
throw assert.fail('Should fail with turned off error');
});
test('test sync status', async () => { test('test sync status', async () => {
const target = new UserDataSyncTestServer(); const target = new UserDataSyncTestServer();
......
...@@ -540,13 +540,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo ...@@ -540,13 +540,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
} }
}); });
if (result.confirmed) { if (result.confirmed) {
await this.disableSync();
if (result.checkboxChecked) { if (result.checkboxChecked) {
this.telemetryService.publicLog2('sync/turnOffEveryWhere'); this.telemetryService.publicLog2('sync/turnOffEveryWhere');
await this.userDataSyncService.reset(); await this.userDataSyncService.reset();
} else { } else {
await this.userDataSyncService.resetLocal(); await this.userDataSyncService.resetLocal();
} }
await this.disableSync();
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册